@djmima/pubmed-mcp 1.0.0 → 1.0.2
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 +35 -0
- package/README.md +827 -793
- package/dist/cli/commands.js +114 -114
- package/dist/cli/run.js +31 -31
- package/dist/pubmed/client.d.ts.map +1 -1
- package/dist/pubmed/client.js +8 -4
- package/dist/pubmed/client.js.map +1 -1
- package/dist/utils/xml.d.ts.map +1 -1
- package/dist/utils/xml.js +2 -1
- package/dist/utils/xml.js.map +1 -1
- package/package.json +3 -2
- package/skills/pubmed-cli/SKILL.md +79 -79
package/README.md
CHANGED
|
@@ -1,793 +1,827 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
NCBI_API_KEY
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
-
|
|
181
|
-
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
###
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
Web
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
**
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1
|
+
<h1 align="center">PubMed MCP</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center"><strong>Search and read the biomedical literature from your AI assistant.</strong></p>
|
|
4
|
+
|
|
5
|
+
<p align="center">PubMed and PubMed Central for Claude, Codex, and the shell.</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="#tools">Tools</a> ·
|
|
9
|
+
<a href="#installation"><strong>Install</strong></a> ·
|
|
10
|
+
<a href="#usage">Usage</a> ·
|
|
11
|
+
<a href="#cli">CLI</a> ·
|
|
12
|
+
<a href="#deploying-to-google-cloud-run">Deploy</a> ·
|
|
13
|
+
<a href="CHANGELOG.md">Changelog</a>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
<a href="https://www.npmjs.com/package/@djmima/pubmed-mcp"><img alt="npm version" src="https://img.shields.io/npm/v/@djmima/pubmed-mcp?label=npm&color=cb3837"></a>
|
|
18
|
+
<a href="https://packagephobia.com/result?p=@djmima/pubmed-mcp"><img alt="npm unpacked size" src="https://img.shields.io/npm/unpacked-size/@djmima/pubmed-mcp"></a>
|
|
19
|
+
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue"></a>
|
|
20
|
+
<a href="https://nodejs.org/"><img alt="Node.js version" src="https://img.shields.io/node/v/@djmima/pubmed-mcp"></a>
|
|
21
|
+
<a href="https://github.com/DJMIMA/PubMed_MCP/actions/workflows/ci.yml"><img alt="CI status" src="https://github.com/DJMIMA/PubMed_MCP/actions/workflows/ci.yml/badge.svg"></a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<p align="center"><strong>English</strong> · <a href="README_ja.md">日本語</a></p>
|
|
25
|
+
|
|
26
|
+
**Free, open source, and runs on your own machine.** An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that gives Claude and other LLM clients structured access to [PubMed](https://pubmed.ncbi.nlm.nih.gov/) and [PubMed Central (PMC)](https://www.ncbi.nlm.nih.gov/pmc/) via the NCBI E-utilities API. No account needed; an NCBI API key is optional.
|
|
27
|
+
|
|
28
|
+
## Tools
|
|
29
|
+
|
|
30
|
+
| Tool | Description |
|
|
31
|
+
|------|-------------|
|
|
32
|
+
| `search_articles` | Search PubMed with PubMed syntax or keywords. Supports ATM diagnostics, field restriction, date filters, sorting, and pagination |
|
|
33
|
+
| `get_article_metadata` | Get the title, abstract, authors, MeSH terms, keywords, and more for given PMIDs |
|
|
34
|
+
| `find_related_articles` | Find similar articles and links to other NCBI databases with ELink |
|
|
35
|
+
| `lookup_article_by_citation` | Look up PMIDs from citation data such as journal, year, volume, and first author |
|
|
36
|
+
| `convert_article_ids` | Convert between PMID, PMCID, and DOI |
|
|
37
|
+
| `get_full_text_article` | Get the full body text of PMC Open Access articles as plain text |
|
|
38
|
+
| `get_copyright_status` | Check whether full text is available (`has_full_text`), the license, the copyright statement, and the OA status. Uses PMC EFetch metadata (the old PMC OA Web Service was retired on 2026-08-25) |
|
|
39
|
+
|
|
40
|
+
The same tools are also available from the shell as the `pubmed` command (see [CLI](#cli)).
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## ATM diagnostics and field restriction in `search_articles`
|
|
45
|
+
|
|
46
|
+
PubMed applies Automatic Term Mapping (ATM) to untagged search terms. ATM expands terms to MeSH,
|
|
47
|
+
journal names, author names, and so on, which can cause unintended translations, or a fallback
|
|
48
|
+
search when a quoted phrase is not in the phrase index.
|
|
49
|
+
|
|
50
|
+
`search_articles` accepts an optional `field` value and restricts every untagged term in the query
|
|
51
|
+
to that field:
|
|
52
|
+
|
|
53
|
+
- `title`
|
|
54
|
+
- `tiab` (Title/Abstract)
|
|
55
|
+
- `mesh`
|
|
56
|
+
- `author`
|
|
57
|
+
- `journal`
|
|
58
|
+
- `affiliation`
|
|
59
|
+
|
|
60
|
+
Terms with an explicit field tag in the query keep their tag, which takes precedence over `field`.
|
|
61
|
+
Without `field`, search behaves as before. `all` is not offered as a value, because it does not
|
|
62
|
+
turn off ATM.
|
|
63
|
+
|
|
64
|
+
The response always includes the following fields, normalized from the NCBI ESearch diagnostics:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"warnings": {
|
|
69
|
+
"phrases_ignored": [],
|
|
70
|
+
"quoted_phrases_not_found": [],
|
|
71
|
+
"output_messages": []
|
|
72
|
+
},
|
|
73
|
+
"errors": {
|
|
74
|
+
"phrases_not_found": [],
|
|
75
|
+
"fields_not_found": []
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
These `errors` are NCBI ESearch diagnostics about search terms and fields, not network errors or
|
|
81
|
+
errors from the MCP tool itself.
|
|
82
|
+
The keys are present as empty arrays even when there are no diagnostics. Before concluding from a
|
|
83
|
+
low or zero count that no literature exists, check `query_translation`, `warnings`, and `errors`.
|
|
84
|
+
To search several terms reliably in the same field, tag each term. For proximity searching when a
|
|
85
|
+
quoted phrase is not in the phrase index, use `[ti:~0]` or `[tiab:~0]`.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Requirements
|
|
90
|
+
|
|
91
|
+
- **Node.js** 22 or later (`npx` and `npm` come with Node.js)
|
|
92
|
+
- **NCBI API key** (optional; without one the server works, but is limited to 3 req/s)
|
|
93
|
+
- **jq** (optional; used to extract the fields you need from [CLI](#cli) output)
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Installation
|
|
98
|
+
|
|
99
|
+
The server is published on npm as [`@djmima/pubmed-mcp`](https://www.npmjs.com/package/@djmima/pubmed-mcp).
|
|
100
|
+
|
|
101
|
+
- **To use it as an MCP server**, you don't need to install anything. Put `npx -y @djmima/pubmed-mcp@latest` in your MCP client's configuration,
|
|
102
|
+
and the latest version is downloaded automatically at startup (see [Usage](#usage) below).
|
|
103
|
+
- **To use the [CLI](#cli)**, install it globally:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm install -g @djmima/pubmed-mcp
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This registers two commands, `pubmed` and `pubmed-mcp`. Update with `npm update -g @djmima/pubmed-mcp`
|
|
110
|
+
and uninstall with `npm rm -g @djmima/pubmed-mcp`.
|
|
111
|
+
|
|
112
|
+
If you want to modify the source code, see [Building from source](#building-from-source).
|
|
113
|
+
|
|
114
|
+
### Setting up an NCBI API key (recommended)
|
|
115
|
+
|
|
116
|
+
With an API key, the rate limit goes from 3 to 8 req/s.
|
|
117
|
+
|
|
118
|
+
1. Create an [NCBI account](https://www.ncbi.nlm.nih.gov/account/) and generate an API key
|
|
119
|
+
2. Set it in the `NCBI_API_KEY` environment variable (see the client-specific settings below)
|
|
120
|
+
|
|
121
|
+
When an MCP client starts the server locally, pass the key in the `env` section of the client configuration.
|
|
122
|
+
`.env` is read from the current directory at startup, so when a client starts the server, the `.env` in
|
|
123
|
+
the project root may not be read.
|
|
124
|
+
|
|
125
|
+
Use an API key owned and managed by the person or organization running this server.
|
|
126
|
+
The repository does not ship an API key. When a response includes the `echo` from the NCBI API,
|
|
127
|
+
the `api_key` value is masked as `<redacted>`.
|
|
128
|
+
|
|
129
|
+
The client applies a simple per-process rate limiter. It sends at most 3 req/s without an API key,
|
|
130
|
+
and 8 req/s with one, leaving a margin below NCBI's limit of 10 req/s (NCBI counts by arrival time, so the margin absorbs network jitter). On HTTP 429 it waits and retries up to twice.
|
|
131
|
+
Parallel calls within one process are spaced out automatically, but if you run multiple instances
|
|
132
|
+
on Cloud Run or elsewhere, keep the total across all instances within NCBI's limit. As a guideline,
|
|
133
|
+
keep a single instance to about 8 concurrent requests with an API key and about 2 without.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Usage
|
|
138
|
+
|
|
139
|
+
### Claude Desktop (local, stdio)
|
|
140
|
+
|
|
141
|
+
Add the server to `mcpServers` in `claude_desktop_config.json`.
|
|
142
|
+
|
|
143
|
+
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
144
|
+
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"mcpServers": {
|
|
149
|
+
"pubmed-local": {
|
|
150
|
+
"command": "npx",
|
|
151
|
+
"args": ["-y", "@djmima/pubmed-mcp@latest"],
|
|
152
|
+
"env": {
|
|
153
|
+
"NCBI_API_KEY": "your_api_key_here"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- If other servers are already registered, add `"pubmed-local": { ... }` alongside them inside `mcpServers` (don't forget the comma after the previous entry).
|
|
161
|
+
- If it doesn't start on Windows, change it to `"command": "cmd"` and `"args": ["/c", "npx", "-y", "@djmima/pubmed-mcp@latest"]`.
|
|
162
|
+
- With `@latest`, the client checks npm for a newer version at every startup. To pin a version, write it as `@djmima/pubmed-mcp@1.0.0`.
|
|
163
|
+
- Naming the server `pubmed-local` makes it easy to tell apart from the remote server (`pubmed-remote`) and from other PubMed connectors with the same tool names.
|
|
164
|
+
|
|
165
|
+
After configuring, quit Claude Desktop completely and restart it. Closing the window may leave the app running in the background (on Windows, quit from the system tray icon).
|
|
166
|
+
Servers in this file are also loaded into sessions opened in the Code tab of Claude Desktop.
|
|
167
|
+
|
|
168
|
+
If the server doesn't start, check the log (Windows: `%APPDATA%\Claude\logs\mcp-server-pubmed-local.log`, macOS: `~/Library/Logs/Claude/mcp-server-pubmed-local.log`).
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
### Claude Code (local, stdio)
|
|
173
|
+
|
|
174
|
+
Register the server with `claude mcp add`. With `--scope user`, it is available in every project.
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
claude mcp add --env NCBI_API_KEY=your_api_key_here --transport stdio --scope user pubmed-local -- npx -y @djmima/pubmed-mcp@latest
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
- Everything after `--` is passed as-is as the command that starts the server.
|
|
181
|
+
- On Windows (not WSL), use `cmd /c npx -y @djmima/pubmed-mcp@latest` after `--`.
|
|
182
|
+
- If the server name comes right after `--env`, it is parsed as a continuation of `KEY=value` and fails, so put another option (here, `--transport stdio`) in between.
|
|
183
|
+
- The registration is saved in `~/.claude.json` (user / local scope) or in `.mcp.json` in the project root (project scope). `mcpServers` written in `~/.claude/settings.json` is not read.
|
|
184
|
+
- If you only use the Code tab of Claude Desktop, the `claude_desktop_config.json` registration above is enough, and you can skip this step.
|
|
185
|
+
|
|
186
|
+
After registering, check the connection with `claude mcp list` or `/mcp` inside Claude Code.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
### Codex (local, stdio)
|
|
191
|
+
|
|
192
|
+
The Codex desktop app, CLI, and IDE extension share one configuration file, `~/.codex/config.toml` (Windows: `%USERPROFILE%\.codex\config.toml`). Add the following block:
|
|
193
|
+
|
|
194
|
+
```toml
|
|
195
|
+
[mcp_servers.pubmed-local]
|
|
196
|
+
command = "npx"
|
|
197
|
+
args = ["-y", "@djmima/pubmed-mcp@latest"]
|
|
198
|
+
|
|
199
|
+
[mcp_servers.pubmed-local.env]
|
|
200
|
+
NCBI_API_KEY = "your_api_key_here"
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
- If it doesn't start on Windows, change it to `command = "cmd"` and `args = ["/c", "npx", "-y", "@djmima/pubmed-mcp@latest"]`.
|
|
204
|
+
- Codex does not pass OS environment variables through to MCP servers. Either write the API key in `env` as above, or add `env_vars = ["NCBI_API_KEY"]` under `[mcp_servers.pubmed-local]` to forward it from Codex's environment.
|
|
205
|
+
|
|
206
|
+
You can also register it from the CLI with `codex mcp add`:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
codex mcp add pubmed-local --env NCBI_API_KEY=your_api_key_here -- npx -y @djmima/pubmed-mcp@latest
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
After configuring, restart Codex (in the desktop app, **Restart** under **Settings → MCP servers**). You can also register the server from **Add server** on the same screen. In the CLI and IDE extension, `/mcp` shows the connected servers.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
### Anthropic API (programmatic use)
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
import os
|
|
220
|
+
|
|
221
|
+
import anthropic
|
|
222
|
+
|
|
223
|
+
client = anthropic.Anthropic()
|
|
224
|
+
response = client.beta.messages.create(
|
|
225
|
+
model="claude-opus-5",
|
|
226
|
+
max_tokens=16000,
|
|
227
|
+
messages=[{
|
|
228
|
+
"role": "user",
|
|
229
|
+
"content": "Find recent papers on CRISPR gene editing in cancer treatment"
|
|
230
|
+
}],
|
|
231
|
+
mcp_servers=[{
|
|
232
|
+
"type": "url",
|
|
233
|
+
"url": "https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/mcp",
|
|
234
|
+
"name": "pubmed",
|
|
235
|
+
# /mcp is protected by OAuth, so an access token is required
|
|
236
|
+
"authorization_token": os.environ["PUBMED_MCP_TOKEN"],
|
|
237
|
+
}],
|
|
238
|
+
tools=[{"type": "mcp_toolset", "mcp_server_name": "pubmed"}],
|
|
239
|
+
betas=["mcp-client-2025-11-20"]
|
|
240
|
+
)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The MCP connector in the Anthropic API can only connect to remote servers exposed over HTTP, so you need to deploy to Cloud Run first.
|
|
244
|
+
See [Deploying to Google Cloud Run](#deploying-to-google-cloud-run) for deployment, and [Client connection settings](#client-connection-settings) for getting an access token.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
### Building from source
|
|
249
|
+
|
|
250
|
+
To modify the source code, clone the repository and build it.
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
git clone https://github.com/DJMIMA/PubMed_MCP.git
|
|
254
|
+
cd PubMed_MCP
|
|
255
|
+
npm install
|
|
256
|
+
npm run build
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
In your MCP client configuration, use `node /absolute/path/to/PubMed_MCP/dist/index.js` instead of `npx -y @djmima/pubmed-mcp@latest`
|
|
260
|
+
(on Windows, write the path with forward slashes, like `C:/path/to/PubMed_MCP/dist/index.js`).
|
|
261
|
+
To use the CLI, run `npm link` next; it registers the `pubmed` and `pubmed-mcp` commands (undo with `npm rm -g @djmima/pubmed-mcp`).
|
|
262
|
+
|
|
263
|
+
`dist/` is not in the repository. After updating the source with `git pull`, run `npm run build` again
|
|
264
|
+
and restart your MCP client. A running server keeps using the old build until it restarts.
|
|
265
|
+
|
|
266
|
+
### Local development
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
# Create a .env file and set your API key
|
|
270
|
+
cp .env.example .env
|
|
271
|
+
# Edit .env and fill in NCBI_API_KEY
|
|
272
|
+
|
|
273
|
+
# Start in stdio mode (default)
|
|
274
|
+
npm run dev
|
|
275
|
+
|
|
276
|
+
# Start in HTTP mode (enabled by setting PORT; the OAuth variables are also required)
|
|
277
|
+
PORT=8080 npm run dev
|
|
278
|
+
# → http://localhost:8080/mcp MCP endpoint (Bearer token required)
|
|
279
|
+
# → http://localhost:8080/health Health check
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
HTTP mode reads `OAUTH_ISSUER`, `OAUTH_JWKS_URL`, and `MCP_RESOURCE_URL` at startup, and exits with the following error if any of them is missing:
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
Fatal error: Error: Missing required environment variable: OAUTH_ISSUER
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Set these values in `.env` before starting (see [Environment variables](#environment-variables) for what each one means).
|
|
289
|
+
To check `/health` or the 401 response, placeholder values in URL form are enough to start the server, but to actually call `/mcp`
|
|
290
|
+
you need an access token issued with `MCP_RESOURCE_URL` as its `aud`. To try the tools without authentication, use stdio mode.
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## CLI
|
|
295
|
+
|
|
296
|
+
The `pubmed` command runs the same tools as the MCP server from the shell. Its output is the same JSON as the MCP tools,
|
|
297
|
+
written to standard output or to a file (`-o`). By saving results to a file and extracting only the fields you need with `jq`,
|
|
298
|
+
agents such as Claude Code and Codex avoid loading large amounts of metadata into their context.
|
|
299
|
+
|
|
300
|
+
For example, the metadata for the top 20 results for `TP53 AND myelodysplastic syndromes[mh]` is about 108 KB,
|
|
301
|
+
but extracting only the PMID, year, and title brings it down to 3.2 KB (about 1/34). A single full text runs to tens of thousands of characters.
|
|
302
|
+
|
|
303
|
+
### Installation
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
npm install -g @djmima/pubmed-mcp
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
You're done when `pubmed --help` prints the help.
|
|
310
|
+
|
|
311
|
+
The NCBI API key is read from the `NCBI_API_KEY` environment variable. Add
|
|
312
|
+
`export NCBI_API_KEY=your_api_key_here` to your shell configuration file (`~/.bashrc`, `~/.zshrc`, etc.), or on Windows run
|
|
313
|
+
`setx NCBI_API_KEY your_api_key_here` once and then reopen your terminal.
|
|
314
|
+
|
|
315
|
+
If you [built from source](#building-from-source) and registered the commands with `npm link`, the CLI also reads `.env` in the repository root
|
|
316
|
+
(create it with `cp .env.example .env`) when the environment variable is not set, so the key is used whichever directory you run it from.
|
|
317
|
+
|
|
318
|
+
### Commands
|
|
319
|
+
|
|
320
|
+
| Command | MCP tool | Input | Items per request |
|
|
321
|
+
|---|---|---|---|
|
|
322
|
+
| `pubmed search <query>` | `search_articles` | Search query (`-` reads from stdin) | — |
|
|
323
|
+
| `pubmed metadata <pmid>...` | `get_article_metadata` | PMIDs | 200 |
|
|
324
|
+
| `pubmed related <pmid>...` | `find_related_articles` | PMIDs | 200 |
|
|
325
|
+
| `pubmed citation --journal <journal> --year <year> ...` | `lookup_article_by_citation` | Options, or a JSON array or JSON Lines with `--input` | 100 |
|
|
326
|
+
| `pubmed convert <id>...` | `convert_article_ids` | PMIDs, PMCIDs, or DOIs (set with `--id-type`) | 200 |
|
|
327
|
+
| `pubmed fulltext <pmcid>...` | `get_full_text_article` | PMCIDs | 20 |
|
|
328
|
+
| `pubmed copyright <pmid>...` | `get_copyright_status` | PMIDs | 50 |
|
|
329
|
+
|
|
330
|
+
- Option names are the MCP parameter names in kebab case (`max_results` → `--max-results`, `date_from` → `--date-from`, and so on). See `pubmed <command> --help` for details.
|
|
331
|
+
- Pass IDs as arguments (separated by spaces or commas), or pass `-` to read them from stdin, one per line.
|
|
332
|
+
- IDs beyond the per-request limit are split automatically, sent in order, and merged into one JSON. The MCP tools' limits (200 for metadata, for example) do not apply.
|
|
333
|
+
- With `-o <file>`, the JSON is written to a file, and the item count and file size are printed to stderr.
|
|
334
|
+
- The exit code is 0 on success, 1 on NCBI or file errors, and 2 on usage errors. Error details go to stderr.
|
|
335
|
+
|
|
336
|
+
### Examples
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
# Search, and check how PubMed interpreted the query
|
|
340
|
+
pubmed search "TP53[tiab] AND myelodysplastic syndromes[mh]" --max-results 200 -o search.json
|
|
341
|
+
jq '{total_count, query_translation, warnings, errors}' search.json
|
|
342
|
+
|
|
343
|
+
# Get metadata for every hit and list PMID, year, journal, and title
|
|
344
|
+
jq -r '.pmids[]' search.json | pubmed metadata - -o meta.json
|
|
345
|
+
jq -r '.articles[] | [.identifiers.pmid, .publication_date.year, .journal.iso_abbreviation, .title] | @tsv' meta.json
|
|
346
|
+
|
|
347
|
+
# Get full text only for articles that have it, and search just the parts you need
|
|
348
|
+
jq -r '.pmids[]' search.json | pubmed copyright - -o rights.json
|
|
349
|
+
jq -r '.statuses[] | select(.has_full_text) | .pmcid' rights.json | pubmed fulltext - -o full.json
|
|
350
|
+
jq -r '.articles[].full_text' full.json | grep -n -i 'venetoclax'
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Using with agents
|
|
354
|
+
|
|
355
|
+
[`skills/pubmed-cli/SKILL.md`](skills/pubmed-cli/SKILL.md) is a Skill that teaches agents how to use the CLI (save results to files and read them with jq, pass IDs together in one call, and so on)
|
|
356
|
+
along with jq recipes. Copy it into your user-level skills folder and it is used in every project.
|
|
357
|
+
Claude Code (including the Code tab of Claude Desktop) reads skills from `~/.claude/skills/`, and Codex from `~/.agents/skills/`
|
|
358
|
+
(on Windows, `~` is `%USERPROFILE%`).
|
|
359
|
+
|
|
360
|
+
The Skill is included in the npm package, so if you installed with `npm install -g`, you can copy it with the following commands
|
|
361
|
+
(on Windows, run them in Git Bash):
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
SKILL_DIR="$(npm root -g)/@djmima/pubmed-mcp/skills/pubmed-cli"
|
|
365
|
+
mkdir -p ~/.claude/skills ~/.agents/skills
|
|
366
|
+
cp -r "$SKILL_DIR" ~/.claude/skills/
|
|
367
|
+
cp -r "$SKILL_DIR" ~/.agents/skills/
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
If you built from source, set `SKILL_DIR=skills/pubmed-cli` in the repository root and run the same commands.
|
|
371
|
+
Copy it again whenever you update the package or the Skill.
|
|
372
|
+
|
|
373
|
+
To stop Claude Code from asking for permission every time it runs a command, add `pubmed` and `jq` to `permissions.allow`
|
|
374
|
+
in `~/.claude/settings.json` (if you already have settings, append to the array):
|
|
375
|
+
|
|
376
|
+
```json
|
|
377
|
+
{
|
|
378
|
+
"permissions": {
|
|
379
|
+
"allow": ["Bash(pubmed:*)", "Bash(jq:*)"]
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Notes:
|
|
385
|
+
|
|
386
|
+
- The CLI works only where a shell is available (Claude Code, Codex, and so on). From claude.ai chats or from a custom connector deployed on Cloud Run, keep using the MCP server.
|
|
387
|
+
- NCBI rate limits apply per process. Running several `pubmed` processes in parallel can exceed the limit in total, so pass IDs together in a single run.
|
|
388
|
+
- On Windows, run pipelines in Git Bash or PowerShell 7. In Windows PowerShell 5.1, `>` saves files as UTF-16, so use `-o` instead.
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Deploying to Google Cloud Run
|
|
393
|
+
|
|
394
|
+
The server can be deployed to Google Cloud Run as a Docker container.
|
|
395
|
+
When the `PORT` environment variable is set, it starts in HTTP mode automatically and talks to MCP clients over the Streamable HTTP transport.
|
|
396
|
+
|
|
397
|
+
In HTTP mode, `/mcp` is **protected by OAuth 2.0**. The server acts as an OAuth resource server and,
|
|
398
|
+
for every request, verifies the Bearer token's (JWT's) signature, issuer (`iss`), audience (`aud`), and expiry against the authorization server's public keys (JWKS).
|
|
399
|
+
The login screen and token issuance are handled by an external authorization server (here, WorkOS AuthKit).
|
|
400
|
+
|
|
401
|
+
Cloud Run itself is exposed with `--allow-unauthenticated`. Cloud Run's IAM authentication (including IAP) is incompatible with connections from claude.ai,
|
|
402
|
+
so access control is done by OAuth in the app rather than by Cloud Run.
|
|
403
|
+
|
|
404
|
+
| Path | Authentication | Description |
|
|
405
|
+
|------|----------------|-------------|
|
|
406
|
+
| `/mcp` | Bearer token required | MCP endpoint |
|
|
407
|
+
| `/.well-known/oauth-protected-resource` | None | Metadata pointing to the authorization server (RFC 9728) |
|
|
408
|
+
| `/health` | None | Health check |
|
|
409
|
+
|
|
410
|
+
```
|
|
411
|
+
Claude (Web / Desktop / mobile), Claude Code, Anthropic API
|
|
412
|
+
│ Authorization: Bearer <access token issued by WorkOS AuthKit>
|
|
413
|
+
▼
|
|
414
|
+
Cloud Run (--allow-unauthenticated)
|
|
415
|
+
│ /mcp verifies the JWT and limits users with ALLOWED_SUBJECTS
|
|
416
|
+
▼
|
|
417
|
+
MCP handler
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
The Claude apps and Claude Code follow the metadata from the 401 response (`WWW-Authenticate` header) returned to requests without a token
|
|
421
|
+
to find the authorization server, and get a token after a login in the browser.
|
|
422
|
+
When using the Anthropic API, the caller prepares the token and passes it in.
|
|
423
|
+
|
|
424
|
+
### Prerequisites
|
|
425
|
+
|
|
426
|
+
- A Google Cloud project (with billing enabled)
|
|
427
|
+
- [gcloud CLI](https://cloud.google.com/sdk/docs/install) installed and logged in
|
|
428
|
+
- A [WorkOS](https://workos.com/) account (AuthKit is used as the OAuth authorization server)
|
|
429
|
+
|
|
430
|
+
### Step 1 — Set up the project and APIs
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
# Set the project to deploy to
|
|
434
|
+
gcloud config set project MY_PROJECT_ID
|
|
435
|
+
|
|
436
|
+
# Enable the required APIs (first time only)
|
|
437
|
+
gcloud services enable \
|
|
438
|
+
run.googleapis.com \
|
|
439
|
+
artifactregistry.googleapis.com \
|
|
440
|
+
cloudbuild.googleapis.com \
|
|
441
|
+
secretmanager.googleapis.com # only if you manage NCBI_API_KEY there
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Step 2 — Create an Artifact Registry repository (first time only)
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
gcloud artifacts repositories create pubmed-mcp \
|
|
448
|
+
--repository-format=docker \
|
|
449
|
+
--location=asia-northeast1 \
|
|
450
|
+
--description="PubMed MCP Server"
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Step 3 — Store secrets in Secret Manager
|
|
454
|
+
|
|
455
|
+
Use Secret Manager so the NCBI API key doesn't end up in your command history or logs.
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
# Store the NCBI API key
|
|
459
|
+
echo -n "your_ncbi_api_key" | \
|
|
460
|
+
gcloud secrets create ncbi-api-key \
|
|
461
|
+
--data-file=- \
|
|
462
|
+
--replication-policy=automatic
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### Step 4 — Build and push the container image
|
|
466
|
+
|
|
467
|
+
With Cloud Build, you can build without installing Docker locally.
|
|
468
|
+
`gcloud builds submit` sends the files in the current directory to Cloud Build, so
|
|
469
|
+
**clone the repository first.**
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
# Clone the repository and move into it
|
|
473
|
+
git clone https://github.com/DJMIMA/PubMed_MCP.git
|
|
474
|
+
cd PubMed_MCP
|
|
475
|
+
|
|
476
|
+
# Build with Cloud Build (no local Docker needed)
|
|
477
|
+
gcloud builds submit \
|
|
478
|
+
--region=asia-northeast1 \
|
|
479
|
+
--tag asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:latest
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
To build with local Docker:
|
|
483
|
+
|
|
484
|
+
```bash
|
|
485
|
+
gcloud auth configure-docker asia-northeast1-docker.pkg.dev
|
|
486
|
+
|
|
487
|
+
docker build -t asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:latest .
|
|
488
|
+
docker push asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:latest
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Step 5 — Set up the OAuth authorization server (WorkOS AuthKit)
|
|
492
|
+
|
|
493
|
+
First, decide the public URL of the MCP endpoint. Cloud Run services are assigned a URL of the form
|
|
494
|
+
`https://SERVICE_NAME-PROJECT_NUMBER.REGION.run.app`, so you can determine it before deploying.
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
PROJECT_NUMBER=$(gcloud projects describe MY_PROJECT_ID --format='value(projectNumber)')
|
|
498
|
+
MCP_RESOURCE_URL="https://pubmed-mcp-${PROJECT_NUMBER}.asia-northeast1.run.app/mcp"
|
|
499
|
+
echo "$MCP_RESOURCE_URL"
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
This URL is `MCP_RESOURCE_URL`. Use **exactly the same string** for the WorkOS Resource Indicator and for the URL you register in each client
|
|
503
|
+
(clients check that the URL they connect to matches the `resource` in the metadata, and the server checks the token's `aud` against this value).
|
|
504
|
+
Cloud Run also assigns a URL of the form `https://pubmed-mcp-xxxx-an.a.run.app`, and both work, but use only one of them for OAuth.
|
|
505
|
+
|
|
506
|
+
Next, configure AuthKit in the WorkOS dashboard.
|
|
507
|
+
|
|
508
|
+
1. Under **Connect → Configuration**, enable **Dynamic Client Registration** (so MCP clients such as claude.ai can register themselves as OAuth clients)
|
|
509
|
+
2. Register `MCP_RESOURCE_URL` as a **Resource Indicator** (access tokens get this URL as their `aud`)
|
|
510
|
+
3. Open the authorization server metadata of your AuthKit domain and note the `issuer` and `jwks_uri` values
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
curl https://YOUR_AUTHKIT_DOMAIN/.well-known/oauth-authorization-server
|
|
514
|
+
# → {"issuer":"https://YOUR_AUTHKIT_DOMAIN", ..., "jwks_uri":"https://YOUR_AUTHKIT_DOMAIN/oauth2/jwks", ...}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
WorkOS Staging and Production environments have separate settings and separate AuthKit domains. When you switch environments, redo the settings above
|
|
518
|
+
and replace `OAUTH_ISSUER` and `OAUTH_JWKS_URL`.
|
|
519
|
+
|
|
520
|
+
### Step 6 — Deploy to Cloud Run
|
|
521
|
+
|
|
522
|
+
Deploy with the values you noted in step 5. Set `ALLOWED_SUBJECTS` to a placeholder value (here, `placeholder`) for now (see step 8 for why).
|
|
523
|
+
|
|
524
|
+
```bash
|
|
525
|
+
# Replace with the issuer and jwks_uri values from step 5
|
|
526
|
+
OAUTH_ISSUER="https://YOUR_AUTHKIT_DOMAIN"
|
|
527
|
+
OAUTH_JWKS_URL="https://YOUR_AUTHKIT_DOMAIN/oauth2/jwks"
|
|
528
|
+
|
|
529
|
+
gcloud run deploy pubmed-mcp \
|
|
530
|
+
--image asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:latest \
|
|
531
|
+
--region asia-northeast1 \
|
|
532
|
+
--port 8080 \
|
|
533
|
+
--memory 512Mi \
|
|
534
|
+
--cpu 1 \
|
|
535
|
+
--timeout 300 \
|
|
536
|
+
--allow-unauthenticated \
|
|
537
|
+
--set-secrets NCBI_API_KEY=ncbi-api-key:latest \
|
|
538
|
+
--set-env-vars "OAUTH_ISSUER=${OAUTH_ISSUER},OAUTH_JWKS_URL=${OAUTH_JWKS_URL},MCP_RESOURCE_URL=${MCP_RESOURCE_URL},ALLOWED_SUBJECTS=placeholder"
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
| Variable | Value |
|
|
542
|
+
|----------|-------|
|
|
543
|
+
| `OAUTH_ISSUER` | The `issuer` from the authorization server metadata. It is compared with the token's `iss` exactly, so copy it as-is, including whether it has a trailing `/` |
|
|
544
|
+
| `OAUTH_JWKS_URL` | The `jwks_uri` from the same metadata: the URL of the public keys used to verify token signatures |
|
|
545
|
+
| `MCP_RESOURCE_URL` | The MCP endpoint URL you decided in step 5. It is compared with the token's `aud`, and published as `resource` in `/.well-known/oauth-protected-resource` |
|
|
546
|
+
| `ALLOWED_SUBJECTS` | A comma-separated list of user IDs (the token's `sub`) allowed to connect. If empty, every user who can get a valid token from AuthKit can connect |
|
|
547
|
+
|
|
548
|
+
If any of `OAUTH_ISSUER`, `OAUTH_JWKS_URL`, or `MCP_RESOURCE_URL` is missing, the server exits at startup with
|
|
549
|
+
`Missing required environment variable: ...` and the deployment fails. If it fails, look for this message in the Cloud Run logs
|
|
550
|
+
(if you set the variables in Cloud Console, also check for spaces around the variable names).
|
|
551
|
+
|
|
552
|
+
> **`--set-env-vars` vs. `--update-env-vars`**: `--set-env-vars` removes all existing environment variables and then sets the ones you specify (variables you leave out are deleted).
|
|
553
|
+
> To add or change only some values after deploying, use `--update-env-vars`, which merges with the existing values (it also works with `gcloud run services update`).
|
|
554
|
+
|
|
555
|
+
### Step 7 — Verify the deployment
|
|
556
|
+
|
|
557
|
+
`/health` and `/.well-known/oauth-protected-resource` respond without authentication. Without a token, `/mcp` **should return 401**.
|
|
558
|
+
|
|
559
|
+
**Linux / macOS (bash)**
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
PROJECT_NUMBER=$(gcloud projects describe MY_PROJECT_ID --format='value(projectNumber)')
|
|
563
|
+
CLOUD_RUN_URL="https://pubmed-mcp-${PROJECT_NUMBER}.asia-northeast1.run.app"
|
|
564
|
+
|
|
565
|
+
# Health check
|
|
566
|
+
curl "$CLOUD_RUN_URL/health"
|
|
567
|
+
# → ok
|
|
568
|
+
|
|
569
|
+
# Protected Resource Metadata (check that resource is MCP_RESOURCE_URL and authorization_servers is OAUTH_ISSUER)
|
|
570
|
+
curl "$CLOUD_RUN_URL/.well-known/oauth-protected-resource"
|
|
571
|
+
# → {"resource":"https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/mcp","authorization_servers":["https://YOUR_AUTHKIT_DOMAIN"],"bearer_methods_supported":["header"]}
|
|
572
|
+
|
|
573
|
+
# MCP endpoint (401 because there is no token; -i shows headers; a heredoc avoids quoting problems)
|
|
574
|
+
curl -i "$CLOUD_RUN_URL/mcp" \
|
|
575
|
+
-X POST \
|
|
576
|
+
-H "Content-Type: application/json" \
|
|
577
|
+
-d @- <<'EOF'
|
|
578
|
+
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"0.0.1"}}}
|
|
579
|
+
EOF
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
The `/mcp` response looks like this (excerpt; the HTTP version and header-name casing vary by environment):
|
|
583
|
+
|
|
584
|
+
```
|
|
585
|
+
HTTP/2 401
|
|
586
|
+
www-authenticate: Bearer resource_metadata="https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/.well-known/oauth-protected-resource", error="missing_token"
|
|
587
|
+
content-type: application/json; charset=utf-8
|
|
588
|
+
|
|
589
|
+
{"error":"missing_token"}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
MCP clients follow `resource_metadata` in the `WWW-Authenticate` header to find the authorization server.
|
|
593
|
+
If token verification fails, the server returns 401 with `error="invalid_token"`; if the token is valid but its `sub` is not in `ALLOWED_SUBJECTS`, it returns 403 (`{"error":"forbidden"}`).
|
|
594
|
+
|
|
595
|
+
**Windows (Command Prompt)**
|
|
596
|
+
|
|
597
|
+
```cmd
|
|
598
|
+
:: Set %CLOUD_RUN_URL% before running (replace PROJECT_NUMBER with your project number)
|
|
599
|
+
set CLOUD_RUN_URL=https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app
|
|
600
|
+
|
|
601
|
+
curl "%CLOUD_RUN_URL%/health"
|
|
602
|
+
|
|
603
|
+
curl "%CLOUD_RUN_URL%/.well-known/oauth-protected-resource"
|
|
604
|
+
|
|
605
|
+
curl -i "%CLOUD_RUN_URL%/mcp" -X POST -H "Content-Type: application/json" -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{},\"clientInfo\":{\"name\":\"test\",\"version\":\"0.0.1\"}}}"
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
**Windows (PowerShell)**
|
|
609
|
+
|
|
610
|
+
```powershell
|
|
611
|
+
$CLOUD_RUN_URL = "https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app"
|
|
612
|
+
|
|
613
|
+
curl.exe "$CLOUD_RUN_URL/health"
|
|
614
|
+
|
|
615
|
+
curl.exe "$CLOUD_RUN_URL/.well-known/oauth-protected-resource"
|
|
616
|
+
|
|
617
|
+
curl.exe -i "$CLOUD_RUN_URL/mcp" -X POST -H "Content-Type: application/json" `
|
|
618
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"0.0.1"}}}'
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Step 8 — Add your user ID to ALLOWED_SUBJECTS
|
|
622
|
+
|
|
623
|
+
With Dynamic Client Registration, anyone can register an OAuth client, so use `ALLOWED_SUBJECTS` to limit which users can connect
|
|
624
|
+
(if it is empty, every user who can log in to AuthKit can use `/mcp`; if AuthKit allows sign-ups, that includes third parties).
|
|
625
|
+
The value is the access token's `sub` (the WorkOS user ID, starting with `user_`).
|
|
626
|
+
|
|
627
|
+
When `ALLOWED_SUBJECTS` is empty, `sub` is not logged. So connect once with the placeholder value in place, and find your `sub` in the log entry for the rejected request.
|
|
628
|
+
|
|
629
|
+
1. Connect from claude.ai or another client following [Client connection settings](#client-connection-settings) below, and log in with WorkOS AuthKit.
|
|
630
|
+
Because your `sub` is not in `ALLOWED_SUBJECTS`, the connection fails with 403, and the server logs `[auth] forbidden subject: ...`.
|
|
631
|
+
2. Find your `sub` in the logs (it is the same as the user ID shown under **Users** in the WorkOS dashboard).
|
|
632
|
+
|
|
633
|
+
```bash
|
|
634
|
+
gcloud run services logs read pubmed-mcp \
|
|
635
|
+
--region asia-northeast1 \
|
|
636
|
+
--log-filter='textPayload:"forbidden subject"' \
|
|
637
|
+
--limit 5
|
|
638
|
+
# → ... [auth] forbidden subject: user_01XXXXXXXXXXXXXXXXXXXXXXXX
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
3. Set that value. Because this uses `--update-env-vars`, the other environment variables stay as they are.
|
|
642
|
+
|
|
643
|
+
```bash
|
|
644
|
+
gcloud run services update pubmed-mcp \
|
|
645
|
+
--region asia-northeast1 \
|
|
646
|
+
--update-env-vars ALLOWED_SUBJECTS=user_01XXXXXXXXXXXXXXXXXXXXXXXX
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
To allow several users, the value contains commas, so change the delimiter with `^@^`
|
|
650
|
+
(for example, `--update-env-vars '^@^ALLOWED_SUBJECTS=user_01AAAA,user_01BBBB'`; in Windows cmd / PowerShell, write `^` as `^^^^`. See `gcloud topic escaping` for details).
|
|
651
|
+
4. Reconnect from the client.
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
### Client connection settings
|
|
656
|
+
|
|
657
|
+
After deploying, register the MCP endpoint URL in each client. Use **exactly the same string** as `MCP_RESOURCE_URL`
|
|
658
|
+
(for example, `https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/mcp`).
|
|
659
|
+
The WorkOS AuthKit login screen opens on the first connection. You can connect if the `sub` of the account you log in with is in `ALLOWED_SUBJECTS`
|
|
660
|
+
(see step 8 for adding your own `sub`).
|
|
661
|
+
|
|
662
|
+
**Claude (Web and Claude Desktop) — custom connector**
|
|
663
|
+
|
|
664
|
+
Add the remote server as a "custom connector" ([Claude Help Center](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp)).
|
|
665
|
+
The Free plan also allows one custom connector.
|
|
666
|
+
|
|
667
|
+
- Pro / Max plans
|
|
668
|
+
1. Open **Customize > Connectors** in [claude.ai](https://claude.ai) or Claude Desktop
|
|
669
|
+
2. Click **+** → **Add custom connector**
|
|
670
|
+
3. Enter the URL (`MCP_RESOURCE_URL`) and click **Add**
|
|
671
|
+
4. Authenticate from **Connect** on the added connector (the WorkOS AuthKit login screen opens)
|
|
672
|
+
- Team / Enterprise plans
|
|
673
|
+
1. An owner opens **Organization settings > Connectors**, clicks **Add**, hovers over **Custom**, and chooses **Web**
|
|
674
|
+
2. Enter the URL (`MCP_RESOURCE_URL`) and click **Add**
|
|
675
|
+
3. Each member finds the connector under **Customize > Connectors** and authenticates from **Connect**
|
|
676
|
+
|
|
677
|
+
Leave OAuth Client ID / OAuth Client Secret under **Advanced settings** empty (the client registers itself with Dynamic Client Registration).
|
|
678
|
+
To use it in a chat, enable it per conversation from **+** → **Connectors** at the bottom left of the input box.
|
|
679
|
+
|
|
680
|
+
Claude Desktop's `claude_desktop_config.json` is for local (stdio) servers; writing `"type"` or `"url"` there does not connect to a remote server.
|
|
681
|
+
On Desktop too, add the remote server as a custom connector as above (connectors added on the Web are available on Desktop as well).
|
|
682
|
+
|
|
683
|
+
**Claude mobile apps (Android / iOS)**
|
|
684
|
+
|
|
685
|
+
Custom connectors added on the Web or in Claude Desktop work as-is in the mobile apps signed in to the same account.
|
|
686
|
+
Adding connectors from the mobile apps is a beta feature, so add them on the Web or in Desktop.
|
|
687
|
+
|
|
688
|
+
**Claude Code**
|
|
689
|
+
|
|
690
|
+
```bash
|
|
691
|
+
claude mcp add --transport http --scope user pubmed-remote https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/mcp
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
Claude Code does not read `mcpServers` in `~/.claude/settings.json`, so register the server with `claude mcp add`.
|
|
695
|
+
After registering, open `/mcp` in Claude Code, choose `pubmed-remote`, and log in to WorkOS AuthKit in the browser to authenticate.
|
|
696
|
+
|
|
697
|
+
**Anthropic API (Python)**
|
|
698
|
+
|
|
699
|
+
```python
|
|
700
|
+
import os
|
|
701
|
+
|
|
702
|
+
import anthropic
|
|
703
|
+
|
|
704
|
+
client = anthropic.Anthropic()
|
|
705
|
+
response = client.beta.messages.create(
|
|
706
|
+
model="claude-opus-5",
|
|
707
|
+
max_tokens=16000,
|
|
708
|
+
messages=[{"role": "user", "content": "..."}],
|
|
709
|
+
mcp_servers=[{
|
|
710
|
+
"type": "url",
|
|
711
|
+
"url": "https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/mcp",
|
|
712
|
+
"name": "pubmed",
|
|
713
|
+
"authorization_token": os.environ["PUBMED_MCP_TOKEN"],
|
|
714
|
+
}],
|
|
715
|
+
tools=[{"type": "mcp_toolset", "mcp_server_name": "pubmed"}],
|
|
716
|
+
betas=["mcp-client-2025-11-20"]
|
|
717
|
+
)
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
The MCP connector neither performs the OAuth login nor refreshes tokens. The caller must get an access token that WorkOS AuthKit issued for this server
|
|
721
|
+
(with `aud` equal to `MCP_RESOURCE_URL` and a `sub` included in `ALLOWED_SUBJECTS`), pass it as `authorization_token`,
|
|
722
|
+
and get a new one when it expires.
|
|
723
|
+
|
|
724
|
+
You can get a token for testing with MCP Inspector ([MCP connector documentation](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector#authentication)):
|
|
725
|
+
|
|
726
|
+
1. Run `npx @modelcontextprotocol/inspector`
|
|
727
|
+
2. Choose **Streamable HTTP** as the **Transport type** and enter `MCP_RESOURCE_URL` as the URL
|
|
728
|
+
3. Click **Open Auth Settings** → **Quick OAuth Flow** and log in with WorkOS AuthKit
|
|
729
|
+
4. Continue until **Authentication complete**, then copy the displayed `access_token` value
|
|
730
|
+
|
|
731
|
+
---
|
|
732
|
+
|
|
733
|
+
### Updating the image (redeploying)
|
|
734
|
+
|
|
735
|
+
```bash
|
|
736
|
+
cd PubMed_MCP
|
|
737
|
+
git pull
|
|
738
|
+
|
|
739
|
+
gcloud builds submit \
|
|
740
|
+
--region=asia-northeast1 \
|
|
741
|
+
--tag asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:latest
|
|
742
|
+
|
|
743
|
+
gcloud run services update pubmed-mcp \
|
|
744
|
+
--region asia-northeast1 \
|
|
745
|
+
--image asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:latest
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
An update that specifies only `--image` keeps the existing environment variables and secrets.
|
|
749
|
+
|
|
750
|
+
---
|
|
751
|
+
|
|
752
|
+
## Development commands
|
|
753
|
+
|
|
754
|
+
```bash
|
|
755
|
+
npm run build # Compile TypeScript → dist/
|
|
756
|
+
npm run dev # Run directly with tsx (for development)
|
|
757
|
+
npm run cli -- search "CRISPR" --max-results 5 # Run the CLI directly with tsx (for development)
|
|
758
|
+
npm test # Run the test suite
|
|
759
|
+
npm run typecheck # Type-check only (no emit)
|
|
760
|
+
npm run lint # Run ESLint
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
### Releasing to npm (for maintainers)
|
|
764
|
+
|
|
765
|
+
Publishing a Release on GitHub makes [`.github/workflows/release.yml`](.github/workflows/release.yml) stage a publish to npm ([staged publishing](https://docs.npmjs.com/staged-publishing/)).
|
|
766
|
+
The new version is not published until a maintainer approves it on npmjs.com with 2FA. This way, a compromised workflow or dependency cannot publish on its own.
|
|
767
|
+
Because the workflow uses npm [Trusted Publishing](https://docs.npmjs.com/trusted-publishers/) (OIDC), no npm token is stored in the repository.
|
|
768
|
+
|
|
769
|
+
PRs that make changes add them to `[Unreleased]` in [`CHANGELOG.md`](CHANGELOG.md). To release:
|
|
770
|
+
|
|
771
|
+
1. On a release branch, run `npm version patch --no-git-tag-version` (or `minor` / `major`) to bump the version in `package.json`
|
|
772
|
+
(GitHub adds the tag in step 3, so don't tag here)
|
|
773
|
+
2. Move the contents of `[Unreleased]` in `CHANGELOG.md` under a heading for the new version (for example, `## [1.0.1] - 2026-11-01`), update the comparison links at the end, then open a PR and merge it into main
|
|
774
|
+
3. On GitHub, publish a Release targeting main with the tag `v<version>` (for example, `v1.0.1`). Paste the corresponding part of `CHANGELOG.md` into the release notes
|
|
775
|
+
4. The workflow checks that the tag matches the version in `package.json`, runs typecheck, lint, tests, and build, and then stages the publish
|
|
776
|
+
5. Review it on the **Staged Packages** tab of the package page on npmjs.com and click **Approve** (2FA required). You can also approve from the CLI with `npm stage approve <stage-id>`
|
|
777
|
+
|
|
778
|
+
As a rule of thumb, bug fixes and documentation changes are `patch`, new tools or options are `minor`,
|
|
779
|
+
and changes to tool names, arguments, or output shape that break existing usage are `major`.
|
|
780
|
+
|
|
781
|
+
The first release is published by hand, because Trusted Publishing can't be configured until the package exists on npm.
|
|
782
|
+
|
|
783
|
+
1. Run `npm login` with an npm account that has 2FA enabled, and publish the latest main with `npm publish` (if it is held for approval, approve it as in step 5 above)
|
|
784
|
+
2. In the package settings on npmjs.com → **Trusted Publisher**, choose GitHub Actions and register the repository `DJMIMA/PubMed_MCP` and the workflow `release.yml`.
|
|
785
|
+
Under **Allowed actions**, allow only `npm stage publish` (always allowed); do not allow `npm publish` or `npm dist-tag`
|
|
786
|
+
3. On the same settings page, under **Publishing access**, choose **Require two-factor authentication and disallow tokens**
|
|
787
|
+
|
|
788
|
+
---
|
|
789
|
+
|
|
790
|
+
## Environment variables
|
|
791
|
+
|
|
792
|
+
| Variable | Required | Description |
|
|
793
|
+
|----------|----------|-------------|
|
|
794
|
+
| `NCBI_API_KEY` | No | NCBI API key. Raises the rate limit from 3 to 8 req/s |
|
|
795
|
+
| `PORT` | No | Starts the server in HTTP mode when set. Cloud Run injects this variable automatically |
|
|
796
|
+
| `OAUTH_ISSUER` | Required in HTTP mode | The authorization server's issuer (`issuer` in its metadata). Compared with the token's `iss` |
|
|
797
|
+
| `OAUTH_JWKS_URL` | Required in HTTP mode | The URL of the authorization server's JWKS (`jwks_uri` in the same metadata). Used to verify token signatures |
|
|
798
|
+
| `MCP_RESOURCE_URL` | Required in HTTP mode | The public URL of the MCP endpoint. Compared with the token's `aud`, and published as `resource` in the Protected Resource Metadata |
|
|
799
|
+
| `ALLOWED_SUBJECTS` | No (recommended) | A comma-separated list of token `sub` values allowed to connect. If empty, every user with a valid token is allowed |
|
|
800
|
+
|
|
801
|
+
`OAUTH_ISSUER`, `OAUTH_JWKS_URL`, `MCP_RESOURCE_URL`, and `ALLOWED_SUBJECTS` are used only in HTTP mode and are not needed in stdio mode.
|
|
802
|
+
See steps 5–8 of [Deploying to Google Cloud Run](#deploying-to-google-cloud-run) for how to choose their values.
|
|
803
|
+
|
|
804
|
+
To use a `.env` file, copy `.env.example` and edit it.
|
|
805
|
+
In production, pass `NCBI_API_KEY` from Secret Manager with Cloud Run's `--set-secrets`, and set the OAuth values with `--set-env-vars` / `--update-env-vars`.
|
|
806
|
+
|
|
807
|
+
---
|
|
808
|
+
|
|
809
|
+
## References
|
|
810
|
+
|
|
811
|
+
- [MCP documentation](https://modelcontextprotocol.io/docs)
|
|
812
|
+
- [NCBI E-utilities documentation](https://www.ncbi.nlm.nih.gov/books/NBK25499/)
|
|
813
|
+
- [NCBI API key registration](https://www.ncbi.nlm.nih.gov/account/)
|
|
814
|
+
- [PubMed search help](https://pubmed.ncbi.nlm.nih.gov/help/)
|
|
815
|
+
- [Cloud Run documentation](https://cloud.google.com/run/docs)
|
|
816
|
+
- [Secret Manager documentation](https://cloud.google.com/secret-manager/docs)
|
|
817
|
+
|
|
818
|
+
---
|
|
819
|
+
|
|
820
|
+
## License
|
|
821
|
+
|
|
822
|
+
The source code in this repository is released under the [MIT License](LICENSE).
|
|
823
|
+
|
|
824
|
+
- This is an unofficial tool, not affiliated with NCBI or NLM (the U.S. National Library of Medicine).
|
|
825
|
+
- Access to PubMed / PMC goes through NCBI's E-utilities. When using it, follow [NCBI's website and data usage policies, including the Disclaimer and Copyright notice](https://www.ncbi.nlm.nih.gov/home/about/policies/), and the [E-utilities usage guidelines](https://www.ncbi.nlm.nih.gov/books/NBK25497/).
|
|
826
|
+
- As the guidelines require, requests to NCBI include this software's name (`tool=pubmed-mcp`) and the developer's contact address (`email`). If you modify this code and distribute it as different software, change `TOOL_NAME` and `TOOL_EMAIL` in `src/pubmed/client.ts` to your own values.
|
|
827
|
+
- The MIT License covers only this software's code. Copyright in retrieved abstracts and full texts belongs to their authors and publishers. Check each article's license with `get_copyright_status`.
|