@pipeworx/mcp-medicare-pricing 0.1.1 → 0.1.3

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 CHANGED
@@ -4,7 +4,7 @@ What Medicare **pays** — physician fee schedule, lab rates, and per-day unit
4
4
  limits. The companion to `medicare-coverage`, which answers whether something is
5
5
  covered but not what it is worth.
6
6
 
7
- Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 1679+ live data sources.
7
+ Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 1683+ live data sources.
8
8
 
9
9
  ## Tools
10
10
 
@@ -12,6 +12,7 @@ Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents
12
12
  |---|---|
13
13
  | `medicare_physician_payment` | "How much does Medicare pay for 99213 in Los Angeles?" — RVUs and dollars, locality-adjusted. |
14
14
  | `medicare_lab_rate` | "What does a comprehensive metabolic panel reimburse?" — national CLFS rate. |
15
+ | `medicare_lab_test_lookup` | "What does Medicare pay for Signatera, and which MolDX LCD applies?" — brand name → PLA code, current CLFS rate, and the MolDX LCDs/articles for the test's category. |
15
16
  | `medicare_dmepos_rate` | "How much does Medicare pay for a CPAP machine (E0601) in Texas?" — state-priced, rural vs non-rural. |
16
17
  | `medicare_code_units_limit` | "How many units of this code will they pay for in one day?" — NCCI MUE. |
17
18
  | `check_code_pair` | "Can I bill 00142 and 64474 together?" — NCCI PTP edit + modifier indicator for a code pair. |
@@ -32,6 +33,69 @@ All from CMS, all downloadable files with no API:
32
33
  - **NCCI Medically Unlikely Edits** — quarterly
33
34
  - **NCCI PTP quarterly additions/deletions/revisions files** — quarterly (see below — a narrower source than the other three)
34
35
 
36
+ ## ADLT list — brand name to billing code
37
+
38
+ `medicare_lab_test_lookup` exists because callers name a test by its brand
39
+ ("Signatera"), not its code ("0340U"), and nothing else in the catalog maps one
40
+ to the other (fleet #2426).
41
+
42
+ **Source, and why it is the only one.** CMS's list of Advanced Diagnostic
43
+ Laboratory Tests
44
+ (<https://www.cms.gov/files/document/advanced-diagnostic-laboratory-tests-under-medicare-clfs.pdf>)
45
+ gives code, lab, proprietary test name, approval date and initial-period
46
+ payment for 18 tests. It is a US federal work (17 USC 105). The sources that
47
+ were checked and rejected:
48
+
49
+ - **AMA PLA long-descriptor PDF** — "CPT® Copyright 2026 American Medical
50
+ Association. All rights reserved." It also lists only the codes changed in
51
+ the latest cycles (143 of them), and 0340U is not among them.
52
+ - **Palmetto MolDX DEX registry** — needs a registered account, even for the
53
+ "public user" catalog, and hides Z-codes from public users. That's an auth
54
+ wall.
55
+ - **CMS Coverage API article code tables** — need a caller's CMS/AMA licence
56
+ token (already how `medicare-coverage` handles it).
57
+ - **NCBI GTR** — knows Signatera and Guardant360 but its `cptcode` field is
58
+ empty for them.
59
+
60
+ That made coverage ADLT-only, and fleet #2458 measured the cost: Oncotype DX
61
+ (81519, a Category I MAAA code, not an ADLT) routed here 5/5 and came back
62
+ `found: false`. Resolution now runs through three sources in order:
63
+
64
+ 1. **CMS ADLT list** (`src/adlt.ts`, below).
65
+ 2. **MAAA brand table** (`src/brand-codes.ts`) -- non-ADLT branded tests whose
66
+ proprietary name is tied to a code in CPT Appendix O and which each have a
67
+ brand-named or category MolDX billing article on the CMS MCD (Oncotype DX
68
+ breast/DCIS/colon/prostate, Prosigna, MammaPrint, EndoPredict, Breast
69
+ Cancer Index, Cologuard, Prolaris, Decipher, Afirma, ConfirmMDx, 4Kscore).
70
+ Only brand -> code -> lab FACTS are kept, never AMA descriptor text. Every
71
+ code was checked to carry a CLFS rate on 2026-09-26. To add a row: confirm
72
+ the pair in a public source, confirm `medicare_lab_rate` has a rate, and set
73
+ `coverage_topic` to a phrase from a live MolDX article title.
74
+ 3. **NCBI GTR, live** -- where the performing lab registered a CPT code
75
+ (e.g. Afirma -> 81546, Decipher -> 81542). Labelled lab-reported. Sparse:
76
+ Oncotype DX and Signatera carry no code there, which is why it is last.
77
+
78
+ A test found in none returns `found: false, reason: brand_not_found` with
79
+ `sources_checked` naming all three, and a pointer to `medicare_lab_rate`.
80
+
81
+ **Copy, not proxy.** The list is a two-page PDF with no API, and the gateway
82
+ has no PDF parser, so the rows are baked into `src/adlt.ts`. The PDF's "Test
83
+ Descriptor" column is AMA PLA text and is **not** copied. Every call sends a
84
+ HEAD to the PDF and compares `Last-Modified` with the snapshot's. If CMS has
85
+ republished, the response carries `snapshot_current: false`. To refresh:
86
+ download the PDF, extract its text (`pypdf` works), update the rows in
87
+ `src/adlt.ts`, and bump `ADLT_DATA_AS_OF` and `SNAPSHOT_LAST_MODIFIED`.
88
+
89
+ **Coverage.** Each test has a `coverage_topic`, a phrase that is ours and not
90
+ CMS's. It matches the title of the MolDX LCD/article family for the test's
91
+ category (for Signatera, "Minimal Residual Disease"). The tool reads the live,
92
+ keyless CMS Coverage API reports and returns the active LCDs and billing
93
+ articles with that title. Retired ones come back only with
94
+ `include_retired: true`. This matters because contractors re-issue MolDX
95
+ policies under new numbers: Noridian's L38816 and A58456 were retired
96
+ 2026-02-05 and replaced by L38814 and A58454. Whether a specific test is listed
97
+ is answered by the billing article's code table, not by the title match.
98
+
35
99
  ## Vintage is not a detail
36
100
 
37
101
  Fee schedules change annually and NCCI/CLFS quarterly. Quoting a 2026 procedure
@@ -247,7 +311,7 @@ directly, instead of just this one's:
247
311
  }
248
312
  ```
249
313
 
250
- Both URLs reach the same gateway and the same 1679+ data sources. The
314
+ Both URLs reach the same gateway and the same 1683+ data sources. The
251
315
  only difference is which pack's tools are listed **directly**; `ask_pipeworx`
252
316
  reaches all of them from either one.
253
317
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipeworx/mcp-medicare-pricing",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "What does Medicare PAY — physician fee schedule, lab rates, and per-day unit",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -26,7 +26,7 @@
26
26
  "@cloudflare/workers-types": "^4.20260405.1"
27
27
  },
28
28
  "pipeworx": {
29
- "sourceHash": "v1-99cba48ac939afdd396bff76c29f505a71d0adef43532b151a0889ee1e98a95a",
30
- "sourceCommit": "adb69d997f82a62e8e22b594488c7d04a227db35"
29
+ "sourceHash": "v1-4bc045e1de966c1a9bd8a24cb7841ad9803cdfe842f2b6db807b64562639bc33",
30
+ "sourceCommit": "6fe8fb3e01138dfc4ebe7d5c246503aab9278d06"
31
31
  }
32
32
  }
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.pipeworx-io/medicare-pricing",
4
4
  "title": "Medicare Pricing",
5
5
  "description": "What does Medicare PAY — physician fee schedule, lab rates, and per-day unit",
6
- "version": "0.1.1",
6
+ "version": "0.1.3",
7
7
  "websiteUrl": "https://pipeworx.io/packs/medicare-pricing",
8
8
  "repository": {
9
9
  "url": "https://github.com/pipeworx-io/mcp-medicare-pricing",
package/src/adlt.ts ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * CMS's list of Advanced Diagnostic Laboratory Tests (ADLTs) — the one public,
3
+ * licence-clean list that maps a lab test's BRAND name to its billing code
4
+ * (fleet #2426: "Signatera" -> 0340U).
5
+ *
6
+ * Source: https://www.cms.gov/files/document/advanced-diagnostic-laboratory-tests-under-medicare-clfs.pdf
7
+ * (linked from cms.gov "ADLT Information"; embedded source name
8
+ * "2025.3.10_List of Approved ADLTs.xlsx"; HTTP Last-Modified
9
+ * Tue, 11 Mar 2025 15:10:55 GMT, 118,376 bytes — checked 2026-09-25).
10
+ *
11
+ * WHY IT IS BAKED, NOT FETCHED. The list is a two-page PDF that changes a few
12
+ * times a year and has no API; the gateway carries no PDF parser. It is a US
13
+ * federal work (17 USC 105), so a copy is fine (CLAUDE.md, "build the copy").
14
+ * The refresh path is the live Last-Modified check in the tool itself
15
+ * (SNAPSHOT_LAST_MODIFIED below): when CMS republishes, every response says
16
+ * so, and the README section "ADLT list" says how to re-extract.
17
+ *
18
+ * WHAT IS NOT HERE. The PDF's "Test Descriptor" column is the AMA's CPT PLA
19
+ * descriptor text ("CPT Copyright ... American Medical Association. All rights
20
+ * reserved") — the same text this pack already refuses to store. Only the CMS
21
+ * facts are kept: code, lab, proprietary test name, approval date, the new-ADLT
22
+ * initial period and its payment.
23
+ *
24
+ * `coverage_topic` is OURS, not CMS's: a phrase that matches the title of the
25
+ * MolDX LCD / billing article for the test's category (each checked against the
26
+ * live LCD title list on 2026-09-25). It finds the policy family; whether this
27
+ * specific test is listed in the article's code table is a separate question
28
+ * the article answers. null where no local policy title fits.
29
+ */
30
+
31
+ export interface AdltTest {
32
+ code: string;
33
+ code_note: string | null;
34
+ lab: string;
35
+ test_name: string;
36
+ aliases: string[];
37
+ adlt_approval_date: string;
38
+ new_adlt_initial_period: string | null;
39
+ initial_period_payment_usd: number | null;
40
+ existing_adlt: boolean;
41
+ coverage_topic: string | null;
42
+ }
43
+
44
+ export const ADLT_SOURCE_URL = 'https://www.cms.gov/files/document/advanced-diagnostic-laboratory-tests-under-medicare-clfs.pdf';
45
+ export const ADLT_DATA_AS_OF = '2025-03-10';
46
+ export const SNAPSHOT_LAST_MODIFIED = 'Tue, 11 Mar 2025 15:10:55 GMT';
47
+
48
+ export const ADLT_TESTS: AdltTest[] = [
49
+ { code: '0537U', code_note: null, lab: 'Guardant Health, Inc.', test_name: 'Shield', aliases: ['Guardant Shield'], adlt_approval_date: '2025-03-10', new_adlt_initial_period: '2025-04-01 to 2025-12-31', initial_period_payment_usd: 1495, existing_adlt: false, coverage_topic: null },
50
+ { code: '0473U', code_note: null, lab: 'Tempus AI, Inc.', test_name: 'xT CDx', aliases: ['Tempus xT', 'Tempus xT CDx'], adlt_approval_date: '2024-06-26', new_adlt_initial_period: '2024-07-01 to 2025-03-31', initial_period_payment_usd: 4500, existing_adlt: false, coverage_topic: 'Next-Generation Sequencing for Solid Tumors' },
51
+ { code: '0356U', code_note: null, lab: 'Naveris, Inc.', test_name: 'NavDx', aliases: [], adlt_approval_date: '2024-03-18', new_adlt_initial_period: '2024-04-01 to 2024-12-31', initial_period_payment_usd: 1800, existing_adlt: false, coverage_topic: 'Minimal Residual Disease' },
52
+ { code: '0315U', code_note: null, lab: 'Castle Biosciences, Inc.', test_name: 'DecisionDx-SCC', aliases: ['DecisionDx SCC'], adlt_approval_date: '2023-06-30', new_adlt_initial_period: '2023-07-01 to 2024-03-31', initial_period_payment_usd: 8500, existing_adlt: false, coverage_topic: 'Risk Stratification of Cutaneous Squamous Cell Carcinoma' },
53
+ { code: '0360U', code_note: null, lab: 'Biodesix, Inc.', test_name: 'Nodify CDT', aliases: [], adlt_approval_date: '2023-06-30', new_adlt_initial_period: null, initial_period_payment_usd: null, existing_adlt: true, coverage_topic: null },
54
+ { code: '0295U', code_note: null, lab: 'Prelude Corporation', test_name: 'DCISionRT', aliases: [], adlt_approval_date: '2023-03-23', new_adlt_initial_period: '2023-04-01 to 2023-12-31', initial_period_payment_usd: 5435, existing_adlt: false, coverage_topic: null },
55
+ { code: '0108U', code_note: null, lab: 'Castle Biosciences, Inc. / Cernostics, Inc.', test_name: "TissueCypher Barrett's Esophagus Assay", aliases: ['TissueCypher'], adlt_approval_date: '2022-03-24', new_adlt_initial_period: '2022-04-01 to 2022-12-31', initial_period_payment_usd: 2350, existing_adlt: false, coverage_topic: 'Upper Gastrointestinal Metaplasia, Dysplasia, and Neoplasia' },
56
+ { code: '0340U', code_note: null, lab: 'Natera, Inc.', test_name: 'Signatera', aliases: [], adlt_approval_date: '2021-06-17', new_adlt_initial_period: '2021-07-01 to 2022-03-31', initial_period_payment_usd: 3500, existing_adlt: false, coverage_topic: 'Minimal Residual Disease' },
57
+ { code: '0242U', code_note: null, lab: 'Guardant Health, Inc.', test_name: 'Guardant360 CDx', aliases: ['Guardant360', 'Guardant 360'], adlt_approval_date: '2021-03-18', new_adlt_initial_period: '2021-04-01 to 2021-12-31', initial_period_payment_usd: 5000, existing_adlt: false, coverage_topic: 'Plasma-Based Genomic Profiling in Solid Tumors' },
58
+ { code: '0239U', code_note: null, lab: 'Foundation Medicine, Inc.', test_name: 'FoundationOne Liquid CDx', aliases: ['F1 Liquid CDx', 'FoundationOne Liquid'], adlt_approval_date: '2021-01-25', new_adlt_initial_period: '2021-04-01 to 2021-12-31', initial_period_payment_usd: 3500, existing_adlt: false, coverage_topic: 'Plasma-Based Genomic Profiling in Solid Tumors' },
59
+ { code: '81554', code_note: 'Effective 2021-01-01', lab: 'Veracyte', test_name: 'Envisia Genomic Classifier', aliases: ['Envisia'], adlt_approval_date: '2020-09-17', new_adlt_initial_period: '2020-10-01 to 2021-06-30', initial_period_payment_usd: 5500, existing_adlt: false, coverage_topic: 'Idiopathic Pulmonary Fibrosis' },
60
+ { code: '0172U', code_note: 'Effective 2020-07-01', lab: 'Myriad', test_name: 'myChoice CDx', aliases: ['Myriad myChoice'], adlt_approval_date: '2019-12-11', new_adlt_initial_period: '2020-01-01 to 2020-09-30', initial_period_payment_usd: 4040, existing_adlt: false, coverage_topic: 'Next-Generation Sequencing for Solid Tumors' },
61
+ { code: '0090U', code_note: null, lab: 'Myriad', test_name: 'myPath Melanoma', aliases: ['Myriad myPath'], adlt_approval_date: '2019-09-06', new_adlt_initial_period: '2019-10-01 to 2020-06-30', initial_period_payment_usd: 1950, existing_adlt: false, coverage_topic: 'Molecular Assays for the Diagnosis of Cutaneous Melanoma' },
62
+ { code: '0080U', code_note: null, lab: 'Biodesix, Inc.', test_name: 'BDX-XL2', aliases: ['BDX XL2'], adlt_approval_date: '2019-05-17', new_adlt_initial_period: '2019-07-01 to 2020-03-31', initial_period_payment_usd: 3520, existing_adlt: false, coverage_topic: null },
63
+ { code: '81529', code_note: 'Effective 2021-01-01', lab: 'Castle Biosciences, Inc.', test_name: 'DecisionDx-Melanoma', aliases: ['DecisionDx Melanoma'], adlt_approval_date: '2019-05-17', new_adlt_initial_period: '2019-07-01 to 2020-03-31', initial_period_payment_usd: 7193, existing_adlt: false, coverage_topic: 'Melanoma Risk Stratification' },
64
+ { code: '81552', code_note: 'Effective 2020-01-01; CMS lists an earlier code for this test ("Previously ...")', lab: 'Castle Biosciences, Inc.', test_name: 'DecisionDx-UM', aliases: ['DecisionDx UM', 'Decision Dx-UM'], adlt_approval_date: '2019-05-17', new_adlt_initial_period: null, initial_period_payment_usd: null, existing_adlt: true, coverage_topic: 'Uveal Melanoma' },
65
+ { code: '81538', code_note: null, lab: 'Biodesix, Inc.', test_name: 'VeriStrat', aliases: [], adlt_approval_date: '2018-12-21', new_adlt_initial_period: null, initial_period_payment_usd: null, existing_adlt: true, coverage_topic: null },
66
+ { code: '0037U', code_note: null, lab: 'Foundation Medicine, Inc.', test_name: 'FoundationOne CDx', aliases: ['F1CDx', 'F1 CDx'], adlt_approval_date: '2018-05-18', new_adlt_initial_period: '2018-07-01 to 2019-03-31', initial_period_payment_usd: 3500, existing_adlt: false, coverage_topic: 'Next-Generation Sequencing for Solid Tumors' },
67
+ ];
68
+
69
+ const norm = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, '');
70
+
71
+ /**
72
+ * Match a caller's free text to ADLT rows: by code, then by test name/alias
73
+ * (either contains the other, so "Signatera MRD test" still resolves), then by
74
+ * lab. A test-name hit wins over a lab hit — "Guardant360" must not return
75
+ * Shield just because both are Guardant's.
76
+ */
77
+ export function matchAdlt(query: string): { matched_by: 'code' | 'test_name' | 'lab' | null; tests: AdltTest[] } {
78
+ const q = norm(query);
79
+ if (q.length < 3) return { matched_by: null, tests: [] };
80
+ const byCode = ADLT_TESTS.filter((t) => norm(t.code) === q);
81
+ if (byCode.length) return { matched_by: 'code', tests: byCode };
82
+ const names = (t: AdltTest) => [t.test_name, ...t.aliases].map(norm);
83
+ const exact = ADLT_TESTS.filter((t) => names(t).includes(q));
84
+ if (exact.length) return { matched_by: 'test_name', tests: exact };
85
+ const byName = ADLT_TESTS.filter((t) => names(t).some((n) => n.length >= 4 && (n.includes(q) || q.includes(n))));
86
+ if (byName.length) {
87
+ // "Guardant360 CDx" contains "guardant360", and so does nothing else; but a
88
+ // query that CONTAINS two names ("FoundationOne CDx") keeps only the longest.
89
+ const longest = Math.max(...byName.map((t) => Math.max(...names(t).filter((n) => q.includes(n)).map((n) => n.length), 0)));
90
+ const narrowed = longest > 0 ? byName.filter((t) => names(t).some((n) => n.length === longest && q.includes(n))) : byName;
91
+ return { matched_by: 'test_name', tests: narrowed };
92
+ }
93
+ const byLab = ADLT_TESTS.filter((t) => q.length >= 4 && norm(t.lab).includes(q));
94
+ if (byLab.length) return { matched_by: 'lab', tests: byLab };
95
+ return { matched_by: null, tests: [] };
96
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Branded lab tests that are NOT ADLTs but carry their own code (fleet #2458).
3
+ *
4
+ * #2426 resolved brand names only through the CMS ADLT list, so the most-asked
5
+ * branded test of all -- Oncotype DX, a Category I MAAA code (81519) -- came
6
+ * back found:false. Two more sources close the class:
7
+ *
8
+ * 1. THIS TABLE: the Category I MAAA tests whose proprietary name is tied to
9
+ * its code in CPT Appendix O, each also the subject of a brand-named MolDX
10
+ * billing-and-coding article on the CMS Medicare Coverage Database. What is
11
+ * kept is the FACT (brand -> code -> lab), never the AMA descriptor text,
12
+ * under the same rule as adlt.ts. Every code here was checked on
13
+ * 2026-09-26 to carry a rate on the loaded CLFS.
14
+ * 2. NCBI GTR (Genetic Testing Registry), fetched LIVE per request: labs
15
+ * register their tests there and many state the CPT code. US government
16
+ * work, keyless E-utilities. It is lab-reported, so the answer says so.
17
+ *
18
+ * `coverage_topic` is a phrase from the title of the test's MolDX policy (a
19
+ * brand-named billing article where one exists), matched live against the CMS
20
+ * Coverage API title list -- ours, not CMS's, checked 2026-09-26.
21
+ */
22
+
23
+ export interface BrandTest {
24
+ code: string;
25
+ lab: string;
26
+ test_name: string;
27
+ aliases: string[];
28
+ coverage_topic: string | null;
29
+ }
30
+
31
+ export const BRAND_SOURCE = 'CPT Appendix O (MAAA) proprietary-name-to-code pairs, each with a brand-named or category MolDX billing article on the CMS Medicare Coverage Database';
32
+ export const BRAND_DATA_AS_OF = '2026-09-26';
33
+
34
+ export const BRAND_TESTS: BrandTest[] = [
35
+ { code: '81519', lab: 'Genomic Health (Exact Sciences)', test_name: 'Oncotype DX Breast Recurrence Score', aliases: ['Oncotype DX', 'Oncotype DX Breast', 'Oncotype DX Breast Cancer Assay', 'Oncotype'], coverage_topic: 'Oncotype DX Breast Cancer Assay' },
36
+ { code: '0045U', lab: 'Genomic Health (Exact Sciences)', test_name: 'Oncotype DX Breast DCIS Score', aliases: ['Oncotype DX DCIS', 'Oncotype DCIS'], coverage_topic: 'Oncotype DX Breast Cancer for DCIS' },
37
+ { code: '81525', lab: 'Genomic Health (Exact Sciences)', test_name: 'Oncotype DX Colon Recurrence Score', aliases: ['Oncotype DX Colon', 'Oncotype DX Colon Cancer Assay'], coverage_topic: 'Oncotype DX Colon Cancer' },
38
+ { code: '0047U', lab: 'Genomic Health (Exact Sciences)', test_name: 'Oncotype DX Genomic Prostate Score', aliases: ['Oncotype DX Prostate', 'Genomic Prostate Score', 'Oncotype GPS'], coverage_topic: 'Prostate Cancer Genomic Classifier Assay' },
39
+ { code: '81518', lab: 'Biotheranostics (Hologic)', test_name: 'Breast Cancer Index', aliases: ['BCI'], coverage_topic: 'Breast Cancer Index' },
40
+ { code: '81520', lab: 'NanoString Technologies (Veracyte)', test_name: 'Prosigna', aliases: ['Prosigna Breast Cancer Prognostic Gene Signature Assay', 'PAM50'], coverage_topic: 'Prosigna' },
41
+ { code: '81521', lab: 'Agendia', test_name: 'MammaPrint', aliases: [], coverage_topic: 'MammaPrint' },
42
+ { code: '81522', lab: 'Myriad Genetics', test_name: 'EndoPredict', aliases: [], coverage_topic: 'EndoPredict' },
43
+ { code: '81528', lab: 'Exact Sciences', test_name: 'Cologuard', aliases: [], coverage_topic: null },
44
+ { code: '81541', lab: 'Myriad Genetics', test_name: 'Prolaris', aliases: [], coverage_topic: 'Prostate Cancer Genomic Classifier Assay' },
45
+ { code: '81542', lab: 'Veracyte (Decipher Biosciences)', test_name: 'Decipher Prostate', aliases: ['Decipher', 'Decipher Prostate Genomic Classifier'], coverage_topic: 'Prostate Cancer Genomic Classifier Assay' },
46
+ { code: '81546', lab: 'Veracyte', test_name: 'Afirma Genomic Sequencing Classifier', aliases: ['Afirma', 'Afirma GSC'], coverage_topic: 'Molecular Testing for Risk Stratification of Thyroid Nodules' },
47
+ { code: '81551', lab: 'MDxHealth', test_name: 'ConfirmMDx', aliases: ['ConfirmMDx for Prostate Cancer'], coverage_topic: 'Molecular Biomarkers to Risk-Stratify Patients at Increased Risk for Prostate Cancer' },
48
+ { code: '81539', lab: 'OPKO Health (BioReference)', test_name: '4Kscore', aliases: ['4K score', '4Kscore Test'], coverage_topic: 'Molecular Biomarkers to Risk-Stratify Patients at Increased Risk for Prostate Cancer' },
49
+ ];
50
+
51
+ export const norm = (s: string) => s.toLowerCase().replace(/[®™]/g, '').replace(/[^a-z0-9]/g, '');
52
+
53
+ type Named = { code: string; test_name: string; aliases: string[]; lab: string };
54
+
55
+ /**
56
+ * Match free text to rows: by code, then exact name/alias, then containment
57
+ * (either way, longest name wins so "Oncotype DX colon" is colon, not breast),
58
+ * then lab. A name hit beats a lab hit.
59
+ */
60
+ export function matchTests<T extends Named>(rows: T[], query: string): { matched_by: 'code' | 'test_name' | 'lab' | null; tests: T[] } {
61
+ const q = norm(query);
62
+ if (q.length < 3) return { matched_by: null, tests: [] };
63
+ const byCode = rows.filter((t) => norm(t.code) === q);
64
+ if (byCode.length) return { matched_by: 'code', tests: byCode };
65
+ const names = (t: T) => [t.test_name, ...t.aliases].map(norm);
66
+ const exact = rows.filter((t) => names(t).includes(q));
67
+ if (exact.length) return { matched_by: 'test_name', tests: exact };
68
+ const byName = rows.filter((t) => names(t).some((n) => n.length >= 4 && (n.includes(q) || q.includes(n))));
69
+ if (byName.length) {
70
+ const longest = Math.max(...byName.map((t) => Math.max(...names(t).filter((n) => q.includes(n)).map((n) => n.length), 0)));
71
+ const narrowed = longest > 0 ? byName.filter((t) => names(t).some((n) => n.length === longest && q.includes(n))) : byName;
72
+ return { matched_by: 'test_name', tests: narrowed };
73
+ }
74
+ const byLab = rows.filter((t) => q.length >= 4 && norm(t.lab).includes(q));
75
+ if (byLab.length) return { matched_by: 'lab', tests: byLab };
76
+ return { matched_by: null, tests: [] };
77
+ }
78
+
79
+ export interface GtrHit { code: string; test_name: string; lab: string; gtr_accession: string; url: string }
80
+
81
+ /**
82
+ * NCBI GTR, live: tests whose registered name matches the query AND whose lab
83
+ * stated a CPT code. null when NCBI did not answer (distinct from "no hit").
84
+ */
85
+ export async function gtrLookup(query: string, fetcher: typeof fetch): Promise<GtrHit[] | null> {
86
+ const q = norm(query);
87
+ if (q.length < 4) return [];
88
+ const base = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils';
89
+ const tool = '&tool=pipeworx&email=support%40pipeworx.io';
90
+ try {
91
+ const s = await fetcher(`${base}/esearch.fcgi?db=gtr&retmode=json&retmax=40&term=${encodeURIComponent(query)}${tool}`);
92
+ if (!s.ok) return null;
93
+ const ids = ((await s.json()) as { esearchresult?: { idlist?: string[] } }).esearchresult?.idlist ?? [];
94
+ if (!ids.length) return [];
95
+ const r = await fetcher(`${base}/esummary.fcgi?db=gtr&retmode=json&id=${ids.join(',')}${tool}`);
96
+ if (!r.ok) return null;
97
+ const res = ((await r.json()) as { result?: Record<string, any> }).result ?? {};
98
+ const hits: GtrHit[] = [];
99
+ for (const id of ids) {
100
+ const x = res[id];
101
+ if (!x || typeof x.testname !== 'string') continue;
102
+ const n = norm(x.testname);
103
+ if (!(n.includes(q) || (n.length >= 4 && q.includes(n)))) continue;
104
+ const codes = String(x.cptcode ?? '').split(/[^0-9A-Za-z]+/).map((c: string) => c.toUpperCase()).filter((c: string) => /^(\d{5}|\d{4}U)$/.test(c));
105
+ for (const code of codes) {
106
+ hits.push({ code, test_name: x.testname, lab: String(x.offerer ?? ''), gtr_accession: String(x.accession ?? ''), url: `https://www.ncbi.nlm.nih.gov/gtr/tests/${id}/` });
107
+ }
108
+ }
109
+ return hits;
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
package/src/index.ts CHANGED
@@ -452,7 +452,42 @@ async function fetchWithTimeout(
452
452
  ),
453
453
  );
454
454
  }
455
- throw err;
455
+ // Fleet #2382. Everything that isn't a timeout/abort here is a genuine
456
+ // NETWORK-LEVEL failure — DNS resolution, connection refused, TLS handshake,
457
+ // Cloudflare's own "Network connection lost." — meaning `fetch()` itself
458
+ // threw and no HTTP response of any kind was ever received. Until this fix
459
+ // that raw exception was rethrown VERBATIM: a bare `TypeError: fetch failed`
460
+ // (or the Workers-runtime equivalent) names no upstream, carries no class
461
+ // token, and reads exactly like a defect in OUR code — because it says
462
+ // nothing about the call at all. It landed in `error`, the tier that means
463
+ // "Pipeworx has a defect", for every one of the (at the time of writing)
464
+ // ~470 packs that call this helper directly with no wrapper of their own.
465
+ //
466
+ // `dexscreener` hit this independently (fleet #1579) and fixed it with a
467
+ // bespoke per-pack try/catch around `fetchWithTimeout`. That fix is correct
468
+ // but only covers one pack; every other caller of this shared helper still
469
+ // leaked the raw exception. Moving the same fix HERE — the one place that
470
+ // already carries the timeout case — covers every pack that uses
471
+ // `fetchWithTimeout` without a wrapper, for free, and without widening
472
+ // `classifyToolError`'s regex list: the fix is giving the message a proper
473
+ // `upstream_down:` token at the point the two facts (no response was ever
474
+ // received, and which host we were trying to reach) are actually in hand,
475
+ // not teaching the classifier to guess from prose after the fact.
476
+ //
477
+ // Safe on the same grounds as the timeout branch above: no argument a
478
+ // caller passes can make `fetch()` itself throw a connection-level error,
479
+ // so this is always an availability failure, never a caller mistake. Same
480
+ // `markInternalOrigin` treatment — an origin we run that never answered is
481
+ // still ours, not a third party's outage.
482
+ const raw = err instanceof Error ? err.message : String(err);
483
+ throw new Error(
484
+ markInternalOrigin(
485
+ `upstream_down: could not reach ${name} at all (${raw.slice(0, 160)}). ` +
486
+ `No request reached ${name}, so this says NOTHING about whether the arguments you passed ` +
487
+ 'are valid — do not re-check them on the strength of this error. Retry shortly.',
488
+ url,
489
+ ),
490
+ );
456
491
  }
457
492
  }
458
493
 
@@ -704,6 +739,8 @@ function collapse(s: string): string {
704
739
  * returns year_not_loaded rather than silently substituting another vintage.
705
740
  */
706
741
 
742
+ import { ADLT_DATA_AS_OF, ADLT_SOURCE_URL, SNAPSHOT_LAST_MODIFIED, matchAdlt, type AdltTest } from './adlt.js';
743
+ import { BRAND_DATA_AS_OF, BRAND_SOURCE, BRAND_TESTS, gtrLookup, matchTests, norm as normName } from './brand-codes.js';
707
744
 
708
745
  // Bound every fetch() in this pack to a fixed timeout — an upstream that
709
746
  // degrades without erroring would otherwise hold the Worker in `await fetch()`
@@ -857,6 +894,19 @@ const tools: McpToolExport['tools'] = [
857
894
  required: ['code'],
858
895
  },
859
896
  },
897
+ {
898
+ name: 'medicare_lab_test_lookup',
899
+ description:
900
+ 'Resolve a branded molecular or genomic lab test by its commercial name (e.g. Signatera, Guardant360 CDx, FoundationOne CDx, FoundationOne Liquid CDx, Tempus xT CDx, DecisionDx-Melanoma, Envisia, Shield) to its CPT PLA / HCPCS billing code, the lab that runs it, what Medicare pays for it now on the Clinical Laboratory Fee Schedule, and the MolDX local coverage determinations (LCDs) and billing-and-coding articles for its test category. Answers "what does Medicare pay for Signatera", "what is the PLA code for Guardant360", "which MolDX LCD covers this test". Also accepts a lab name (e.g. "Natera") or a code. Also covers non-ADLT branded MAAA tests (Oncotype DX, Prosigna, MammaPrint, EndoPredict, Breast Cancer Index, Cologuard, Prolaris, Decipher, Afirma, ConfirmMDx, 4Kscore) and any test whose lab registered its code in the NCBI Genetic Testing Registry. Resolves in order: the CMS list of Advanced Diagnostic Laboratory Tests (ADLTs), a brand-to-code table of MAAA tests with MolDX billing articles, then NCBI GTR live; a test found in none returns brand_not_found naming every source checked. Sourced from CMS (ADLT list, Clinical Laboratory Fee Schedule, Medicare Coverage Database) and NCBI GTR.',
901
+ inputSchema: {
902
+ type: 'object' as const,
903
+ properties: {
904
+ test: { type: 'string', description: 'The test\'s brand or commercial name ("Signatera", "Guardant360 CDx"), the lab ("Natera"), or its code ("0340U").' },
905
+ include_retired: { type: 'boolean', description: 'Also list retired LCDs/articles for the test category (default false). Contractors re-issue MolDX policies under new numbers, so an older citation may be a retired document.' },
906
+ },
907
+ required: ['test'],
908
+ },
909
+ },
860
910
  {
861
911
  name: 'medicare_dmepos_rate',
862
912
  description:
@@ -925,6 +975,57 @@ const tools: McpToolExport['tools'] = [
925
975
  },
926
976
  ];
927
977
 
978
+ /**
979
+ * The ADLT table is a baked copy of a CMS PDF, so every answer re-checks the
980
+ * PDF's Last-Modified against the snapshot. A changed date means CMS has
981
+ * republished the list and the copy may be missing a test — said, not hidden.
982
+ */
983
+ async function adltSnapshotCheck(): Promise<{ snapshot_current: boolean | null; snapshot_note?: string }> {
984
+ try {
985
+ const res = await pwFetch(ADLT_SOURCE_URL, { method: 'HEAD', headers: { 'User-Agent': 'Mozilla/5.0 (compatible; Pipeworx/1.0; +https://pipeworx.io)' } });
986
+ const lm = res.ok ? res.headers.get('last-modified') : null;
987
+ if (!lm) return { snapshot_current: null, snapshot_note: `Could not re-check the CMS ADLT list (HTTP ${res.status}); results reflect the list as of ${ADLT_DATA_AS_OF}.` };
988
+ if (lm === SNAPSHOT_LAST_MODIFIED) return { snapshot_current: true };
989
+ return { snapshot_current: false, snapshot_note: `CMS republished the ADLT list (Last-Modified ${lm}) after the version these results reflect (${SNAPSHOT_LAST_MODIFIED}); a newly approved ADLT may be missing from these results.` };
990
+ } catch {
991
+ return { snapshot_current: null, snapshot_note: `Could not re-check the CMS ADLT list; results reflect the list as of ${ADLT_DATA_AS_OF}.` };
992
+ }
993
+ }
994
+
995
+ interface CoverageDoc {
996
+ document_display_id: string; document_type: string; title: string; contractor: string;
997
+ effective_date: string; retired: boolean; url: string;
998
+ }
999
+
1000
+ /** CMS writes retirement_date as MM/DD/YYYY or "N/A"; a future date is still in effect. */
1001
+ function retiredBy(d: string): boolean {
1002
+ const m = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(d);
1003
+ return !!m && Date.UTC(+m[3], +m[1] - 1, +m[2]) <= Date.now();
1004
+ }
1005
+
1006
+ /** Every final LCD and local article, keyless from the CMS Coverage API reports. null if CMS did not answer. */
1007
+ async function coverageIndex(): Promise<CoverageDoc[] | null> {
1008
+ try {
1009
+ const get = async (path: string) => {
1010
+ const res = await pwFetch(`https://api.coverage.cms.gov/v1/reports/${path}/?page_size=5000`, { headers: { Accept: 'application/json' } });
1011
+ if (!res.ok) throw new Error(String(res.status));
1012
+ return ((await res.json()) as { data?: Record<string, unknown>[] }).data ?? [];
1013
+ };
1014
+ const [lcds, articles] = await Promise.all([get('local-coverage-final-lcds'), get('local-coverage-articles')]);
1015
+ return [...lcds, ...articles].map((r) => ({
1016
+ document_display_id: String(r.document_display_id ?? ''),
1017
+ document_type: String(r.document_type ?? ''),
1018
+ title: String(r.title ?? ''),
1019
+ contractor: String(r.contractor_name_type ?? '').split(/\r?\n/)[0],
1020
+ effective_date: String(r.effective_date ?? ''),
1021
+ retired: /retired/i.test(String(r.note ?? '')) || retiredBy(String(r.retirement_date ?? '')),
1022
+ url: String(r.url ?? ''),
1023
+ }));
1024
+ } catch {
1025
+ return null;
1026
+ }
1027
+ }
1028
+
928
1029
  async function resolveLocality(cfg: Cfg, q: unknown, year: number): Promise<{ chosen: Gpci | null; alternatives: Gpci[]; note: string }> {
929
1030
  if (typeof q !== 'string' || !q.trim()) {
930
1031
  return { chosen: null, alternatives: [], note: 'No locality given, so no geographic adjustment was applied — the RVUs below are national and the payment figures are omitted. Pass locality for an actual dollar amount.' };
@@ -1032,6 +1133,111 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<un
1032
1133
  };
1033
1134
  }
1034
1135
 
1136
+ case 'medicare_lab_test_lookup': {
1137
+ const test = typeof args.test === 'string' ? args.test.trim() : '';
1138
+ if (!test) return { found: false, reason: 'no_test', hint: 'Pass test, e.g. "Signatera".' };
1139
+ const includeRetired = args.include_retired === true;
1140
+ // Sources in order (fleet #2458): the CMS ADLT list, then the MAAA brand
1141
+ // table, then NCBI GTR live. The first that resolves answers; a miss
1142
+ // names every source checked.
1143
+ type Row = { code: string; test_name: string; lab: string; coverage_topic: string | null; code_source: string; adlt?: AdltTest; gtr?: { accession: string; url: string } };
1144
+ const sourcesChecked: string[] = ['CMS ADLT list'];
1145
+ let matched_by: string | null = null;
1146
+ let rows: Row[] = [];
1147
+ let sourceLabel = 'CMS list of Advanced Diagnostic Laboratory Tests (ADLTs) under the Medicare CLFS';
1148
+ const adltHit = matchAdlt(test);
1149
+ if (adltHit.tests.length) {
1150
+ matched_by = adltHit.matched_by;
1151
+ rows = adltHit.tests.map((t) => ({ code: t.code, test_name: t.test_name, lab: t.lab, coverage_topic: t.coverage_topic, code_source: 'CMS ADLT list', adlt: t }));
1152
+ } else {
1153
+ sourcesChecked.push('MAAA brand table (CPT Appendix O pairs backed by MolDX billing articles)');
1154
+ const b = matchTests(BRAND_TESTS, test);
1155
+ if (b.tests.length) {
1156
+ matched_by = b.matched_by;
1157
+ sourceLabel = BRAND_SOURCE;
1158
+ rows = b.tests.map((t) => ({ code: t.code, test_name: t.test_name, lab: t.lab, coverage_topic: t.coverage_topic, code_source: 'MAAA brand table' }));
1159
+ } else {
1160
+ sourcesChecked.push('NCBI Genetic Testing Registry (live)');
1161
+ const g = await gtrLookup(test, ((u: string) => pwFetch(u)) as unknown as typeof fetch);
1162
+ if (g === null) sourcesChecked[sourcesChecked.length - 1] += ' — did not answer';
1163
+ else if (g.length) {
1164
+ matched_by = 'test_name';
1165
+ sourceLabel = 'NCBI Genetic Testing Registry (GTR), code as registered by the performing lab';
1166
+ const seen = new Set<string>();
1167
+ rows = g.filter((h) => !seen.has(h.code + h.lab) && seen.add(h.code + h.lab)).slice(0, 10)
1168
+ .map((h) => ({ code: h.code, test_name: h.test_name, lab: h.lab, coverage_topic: null, code_source: 'NCBI GTR (lab-reported)', gtr: { accession: h.gtr_accession, url: h.url } }));
1169
+ }
1170
+ }
1171
+ }
1172
+ const fromAdlt = rows.some((r) => r.adlt);
1173
+ const [snapshot, coverage] = await Promise.all([
1174
+ fromAdlt || !rows.length ? adltSnapshotCheck() : Promise.resolve({}),
1175
+ rows.some((t) => t.coverage_topic) ? coverageIndex() : Promise.resolve(null),
1176
+ ]);
1177
+ const provenance = {
1178
+ source: `${sourceLabel}; rates from the CMS Clinical Laboratory Fee Schedule; policies from the CMS Medicare Coverage Database`,
1179
+ source_url: fromAdlt || !rows.length ? ADLT_SOURCE_URL : rows[0].gtr ? 'https://www.ncbi.nlm.nih.gov/gtr/' : 'https://www.cms.gov/medicare-coverage-database/',
1180
+ data_as_of: fromAdlt || !rows.length ? ADLT_DATA_AS_OF : rows[0].gtr ? new Date().toISOString().slice(0, 10) : BRAND_DATA_AS_OF,
1181
+ ...snapshot,
1182
+ };
1183
+ if (!rows.length) {
1184
+ return {
1185
+ found: false,
1186
+ reason: 'brand_not_found',
1187
+ test,
1188
+ sources_checked: sourcesChecked,
1189
+ hint: `No billing code found for "${test}" in: ${sourcesChecked.join('; ')}. The full PLA code list with proprietary names is published by the AMA under copyright and is not used here. If you have the code (PLA codes end in U, e.g. "0340U"), medicare_lab_rate gives its rate; medicare_lcd_search finds coverage policies by topic.`,
1190
+ ...provenance,
1191
+ };
1192
+ }
1193
+ const results = await Promise.all(rows.map(async (t) => {
1194
+ const rates = await pg<{ year: number; quarter: string; rate: number }>(cfg,
1195
+ `clfs_rates?hcpcs=eq.${t.code}&select=year,quarter,rate&order=year.desc,quarter.desc&limit=1`);
1196
+ // "Response to Comments" articles are the comment period's record, not policy.
1197
+ const topic = t.coverage_topic ? normName(t.coverage_topic) : '';
1198
+ const docs = topic && coverage
1199
+ ? coverage.filter((d) => normName(d.title).includes(topic) && !/^response to comments/i.test(d.title))
1200
+ : [];
1201
+ const active = docs.filter((d) => !d.retired);
1202
+ const retired = docs.filter((d) => d.retired);
1203
+ const a = t.adlt;
1204
+ return {
1205
+ code: t.code,
1206
+ ...(a?.code_note ? { code_note: a.code_note } : {}),
1207
+ test_name: t.test_name,
1208
+ lab: t.lab,
1209
+ code_source: t.code_source,
1210
+ ...(t.gtr ? { gtr: t.gtr } : {}),
1211
+ current_clfs_rate: rates.length
1212
+ ? { rate_usd: rates[0].rate, fee_schedule_year: rates[0].year, quarter: rates[0].quarter, note: 'National rate; the CLFS is not adjusted by locality.' }
1213
+ : { rate_usd: null, note: `${t.code} has no rate on the loaded Clinical Laboratory Fee Schedule (it may be contractor-priced).` },
1214
+ adlt: a
1215
+ ? { approval_date: a.adlt_approval_date, existing_adlt: a.existing_adlt, new_adlt_initial_period: a.new_adlt_initial_period, initial_period_payment_usd: a.initial_period_payment_usd }
1216
+ : { is_adlt: false },
1217
+ coverage: t.coverage_topic
1218
+ ? {
1219
+ policy_topic: t.coverage_topic,
1220
+ topic_note: 'Policies whose title matches this test or its category (our topic mapping, not CMS\'s). Whether this specific test is listed is stated in the billing article\'s code table.',
1221
+ active_documents: active.slice(0, 20),
1222
+ active_count: active.length,
1223
+ ...(includeRetired ? { retired_documents: retired.slice(0, 20) } : {}),
1224
+ retired_count: retired.length,
1225
+ ...(coverage === null ? { error: 'CMS Coverage API did not answer; retry, or use medicare_lcd_search with the policy_topic.' } : {}),
1226
+ }
1227
+ : { policy_topic: null, note: 'No MolDX local policy title matched to this test; it may be covered nationally (NCD) or case by case — medicare_lcd_search can look.' },
1228
+ };
1229
+ }));
1230
+ return {
1231
+ found: true,
1232
+ query: test,
1233
+ matched_by,
1234
+ tests: results,
1235
+ sources_checked: sourcesChecked,
1236
+ no_descriptors_note: 'The AMA\'s CPT descriptor text is not returned (AMA copyright); only the code, test name and lab.',
1237
+ ...provenance,
1238
+ };
1239
+ }
1240
+
1035
1241
  case 'medicare_dmepos_rate': {
1036
1242
  const hcpcs = code(args.code);
1037
1243
  if (!hcpcs) return { found: false, reason: 'no_code', hint: 'Pass code, e.g. "E0601".' };
package/src/server.ts CHANGED
@@ -9,7 +9,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
9
9
  import pack from './index.js';
10
10
 
11
11
  const server = new Server(
12
- { name: '@pipeworx/mcp-medicare-pricing', version: '0.1.1' },
12
+ { name: '@pipeworx/mcp-medicare-pricing', version: '0.1.3' },
13
13
  { capabilities: { tools: {} } },
14
14
  );
15
15