@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/README.md CHANGED
@@ -1,793 +1,827 @@
1
- # PubMed MCP Server
2
-
3
- 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.
4
-
5
- ## Tools
6
-
7
- | Tool | 説明 |
8
- |------|------|
9
- | `search_articles` | PubMed構文またはキーワードで文献を検索。ATM診断・フィールド制限・日付フィルタ・ソート・ページネーション対応 |
10
- | `get_article_metadata` | PMIDを指定してタイトル・抄録・著者・MeSH語・キーワードなどを取得 |
11
- | `find_related_articles` | ELinkを使って関連文献や他DBへのリンクを検索 |
12
- | `lookup_article_by_citation` | 雑誌名・年・巻・筆頭著者などの引用情報からPMIDを逆引き |
13
- | `convert_article_ids` | PMID・PMCID・DOI を相互変換 |
14
- | `get_full_text_article` | PMC Open Access記事の本文全文をプレーンテキストで取得 |
15
- | `get_copyright_status` | 全文取得の可否(`has_full_text`)・ライセンス・著作権表示・OAステータスを確認。PMCのEFetchメタデータを使用(旧PMC OA Web Serviceは2026-08-25に廃止) |
16
-
17
- 同じツールは、シェルから `pubmed` コマンドとしても実行できます(「[CLI](#cli)」を参照)。
18
-
19
- ---
20
-
21
- ## `search_articles` の ATM 診断とフィールド制限
22
-
23
- PubMed はタグのない検索語に Automatic Term Mapping(ATM)を適用します。ATM は MeSH、
24
- 雑誌名、著者名などへ検索語を展開するため、意図しない変換や、引用句が句インデックスに
25
- 存在しないことによる検索のフォールバックが起こる場合があります。
26
-
27
- `search_articles` は次の任意の `field` 値を受け取り、クエリ内のタグなし語をすべてその
28
- フィールドへ制限します。
29
-
30
- - `title`
31
- - `tiab`(Title/Abstract)
32
- - `mesh`
33
- - `author`
34
- - `journal`
35
- - `affiliation`
36
-
37
- クエリ内に明示的なフィールドタグがある語では、そのタグが `field` より優先されます。
38
- `field` を省略した場合の検索動作は従来どおりです。`all` は ATM を無効化しないため、
39
- 公開パラメータには含めていません。
40
-
41
- レスポンスには、NCBI ESearch の診断を正規化した次のフィールドが常に含まれます。
42
-
43
- ```json
44
- {
45
- "warnings": {
46
- "phrases_ignored": [],
47
- "quoted_phrases_not_found": [],
48
- "output_messages": []
49
- },
50
- "errors": {
51
- "phrases_not_found": [],
52
- "fields_not_found": []
53
- }
54
- }
55
- ```
56
-
57
- この `errors` は NCBI ESearch が返す検索語・フィールドの診断であり、通信エラーや
58
- MCP ツール自体の実行エラーとは別の情報です。
59
- これらのキーは診断がない場合も空配列として返ります。低件数や0件を「文献なし」と判断する
60
- 前に、`query_translation`、`warnings`、`errors` を確認してください。複数語を同一フィールドで
61
- 確実に検索する場合は各語へタグを付け、引用句が句インデックスにない場合の隣接検索には
62
- `[ti:~0]` または `[tiab:~0]` を使用できます。
63
-
64
- ---
65
-
66
- ## 必要環境
67
-
68
- - **Node.js** 22 以上(`npx` と `npm` は Node.js に同梱されています)
69
- - **NCBI API キー**(任意。なくても動作するが、レートリミットが 3 req/s に制限される)
70
- - **jq**(任意。[CLI](#cli) の出力から必要なフィールドを取り出すのに使う)
71
-
72
- ---
73
-
74
- ## インストール
75
-
76
- npm パッケージ [`@djmima/pubmed-mcp`](https://www.npmjs.com/package/@djmima/pubmed-mcp) として公開しています。
77
-
78
- - **MCP サーバーとして使う場合**は、インストールは不要です。MCP クライアントの設定に `npx -y @djmima/pubmed-mcp@latest` を書くと、
79
- 起動時に最新版が自動でダウンロードされます(後述の「[使い方](#使い方)」を参照)。
80
- - **[CLI](#cli) を使う場合**は、グローバルにインストールします。
81
-
82
- ```bash
83
- npm install -g @djmima/pubmed-mcp
84
- ```
85
-
86
- `pubmed` と `pubmed-mcp` の 2 つのコマンドが登録されます。更新は `npm update -g @djmima/pubmed-mcp`、
87
- 削除は `npm rm -g @djmima/pubmed-mcp` です。
88
-
89
- ソースコードを修正して使いたい場合は「[ソースからビルドする](#ソースからビルドする)」を参照してください。
90
-
91
- ### NCBI API キーの設定(推奨)
92
-
93
- APIキーを取得するとレートリミットが 3 → 8 req/s に向上します。
94
-
95
- 1. [NCBI アカウント](https://www.ncbi.nlm.nih.gov/account/)を作成してAPIキーを発行
96
- 2. 環境変数 `NCBI_API_KEY` にセット(後述のクライアント別設定を参照)
97
-
98
- MCP クライアントからローカルで起動する場合は、クライアント設定の `env` でキーを渡してください。
99
- `.env` は起動時のカレントディレクトリから読み込まれるため、クライアント経由の起動では
100
- プロジェクト直下の `.env` が読まれないことがあります。
101
-
102
- APIキーは、このサーバーを運用する個人または組織が所有・管理するものを設定してください。
103
- リポジトリにはAPIキーを同梱しません。レスポンス中にNCBI APIの `echo` が含まれる場合も、
104
- `api_key` 値は `<redacted>` にマスクされます。
105
-
106
- クライアント側ではプロセスごとに簡易レートリミッターをかけています。APIキーなしでは最大
107
- 3 req/s、APIキーありではNCBI上限の10 req/sに対して余裕を持たせた8 req/sで送信します(NCBIは到着時刻で数えるため、回線の揺らぎ分の余裕)。HTTP 429 が返った場合は最大2回まで待機して再試行します。
108
- 同一プロセス内の並列呼び出しは自動的に間隔調整されますが、Cloud Runなどで複数インスタンスを
109
- 動かす場合は全インスタンス合算でNCBIの上限を超えないようにしてください。目安として、単一
110
- インスタンスではAPIキーありで8並列程度、APIキーなしで2並列程度までに抑える運用を推奨します。
111
-
112
- ---
113
-
114
- ## 使い方
115
-
116
- ### Claude Desktop(ローカル・stdio)
117
-
118
- `claude_desktop_config.json` の `mcpServers` にサーバーを追加します。
119
-
120
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
121
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
122
-
123
- ```json
124
- {
125
- "mcpServers": {
126
- "pubmed-local": {
127
- "command": "npx",
128
- "args": ["-y", "@djmima/pubmed-mcp@latest"],
129
- "env": {
130
- "NCBI_API_KEY": "your_api_key_here"
131
- }
132
- }
133
- }
134
- }
135
- ```
136
-
137
- - 他のサーバーが登録済みの場合は、`mcpServers` の中に `"pubmed-local": { ... }` を並べて追加します(前の項目との間のカンマを忘れずに)。
138
- - Windows で起動しない場合は、`"command": "cmd"`、`"args": ["/c", "npx", "-y", "@djmima/pubmed-mcp@latest"]` に変えてください。
139
- - `@latest` を付けると、起動のたびに npm に最新版があるか確認します。版を固定したい場合は `@djmima/pubmed-mcp@1.0.0` のように書きます。
140
- - サーバー名を `pubmed-local` にしておくと、リモート版(`pubmed-remote`)や同じツール名を持つ他の PubMed コネクタと区別しやすくなります。
141
-
142
- 設定後、Claude Desktop を完全に終了してから再起動してください。ウィンドウを閉じただけではアプリが常駐したままになることがあります(Windows ではタスクトレイのアイコンから終了できます)。
143
- この設定のサーバーは、Claude Desktop の Code タブで開いたセッションにも読み込まれます。
144
-
145
- 起動しない場合は、ログ(Windows: `%APPDATA%\Claude\logs\mcp-server-pubmed-local.log`、macOS: `~/Library/Logs/Claude/mcp-server-pubmed-local.log`)を確認してください。
146
-
147
- ---
148
-
149
- ### Claude Code(ローカル・stdio)
150
-
151
- `claude mcp add` で登録します。`--scope user` を付けるとすべてのプロジェクトで使えます。
152
-
153
- ```bash
154
- claude mcp add --env NCBI_API_KEY=your_api_key_here --transport stdio --scope user pubmed-local -- npx -y @djmima/pubmed-mcp@latest
155
- ```
156
-
157
- - `--` より後ろは、サーバーの起動コマンドとしてそのまま渡されます。
158
- - Windows(WSL ではない環境)では、`--` の後ろを `cmd /c npx -y @djmima/pubmed-mcp@latest` にしてください。
159
- - `--env` の直後にサーバー名を書くと `KEY=value` の続きとして解釈されてエラーになるため、間に別のオプション(ここでは `--transport stdio`)を挟みます。
160
- - 登録内容は `~/.claude.json`(user / local スコープ)またはプロジェクト直下の `.mcp.json`(project スコープ)に保存されます。`~/.claude/settings.json` に `mcpServers` を書いても読み込まれません。
161
- - Claude Desktop の Code タブだけで使う場合は、上記の `claude_desktop_config.json` への登録で読み込まれるため、この手順は不要です。
162
-
163
- 登録後、`claude mcp list` または Claude Code 内の `/mcp` で接続状態を確認できます。
164
-
165
- ---
166
-
167
- ### Codex(ローカル・stdio)
168
-
169
- Codex のデスクトップアプリ・CLI・IDE 拡張は、同じ設定ファイル `~/.codex/config.toml`(Windows: `%USERPROFILE%\.codex\config.toml`)を共有します。次のブロックを追記してください。
170
-
171
- ```toml
172
- [mcp_servers.pubmed-local]
173
- command = "npx"
174
- args = ["-y", "@djmima/pubmed-mcp@latest"]
175
-
176
- [mcp_servers.pubmed-local.env]
177
- NCBI_API_KEY = "your_api_key_here"
178
- ```
179
-
180
- - Windows で起動しない場合は、`command = "cmd"`、`args = ["/c", "npx", "-y", "@djmima/pubmed-mcp@latest"]` に変えてください。
181
- - Codex は OS の環境変数をそのまま MCP サーバーへ渡しません。API キーは上のように `env` に書くか、`[mcp_servers.pubmed-local]` に `env_vars = ["NCBI_API_KEY"]` を追加して Codex 側の環境変数から転送してください。
182
-
183
- CLI からは `codex mcp add` でも登録できます。
184
-
185
- ```bash
186
- codex mcp add pubmed-local --env NCBI_API_KEY=your_api_key_here -- npx -y @djmima/pubmed-mcp@latest
187
- ```
188
-
189
- 設定後は Codex を再起動してください(デスクトップアプリでは **Settings → MCP servers** の **Restart**)。同じ画面の **Add server** から登録することもできます。CLI と IDE 拡張では `/mcp` で接続中のサーバーを確認できます。
190
-
191
- ---
192
-
193
- ### Anthropic API(プログラムからの利用)
194
-
195
- ```python
196
- import os
197
-
198
- import anthropic
199
-
200
- client = anthropic.Anthropic()
201
- response = client.beta.messages.create(
202
- model="claude-opus-5",
203
- max_tokens=16000,
204
- messages=[{
205
- "role": "user",
206
- "content": "Find recent papers on CRISPR gene editing in cancer treatment"
207
- }],
208
- mcp_servers=[{
209
- "type": "url",
210
- "url": "https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/mcp",
211
- "name": "pubmed",
212
- # /mcp は OAuth で保護されているため、アクセストークンが必要
213
- "authorization_token": os.environ["PUBMED_MCP_TOKEN"],
214
- }],
215
- tools=[{"type": "mcp_toolset", "mcp_server_name": "pubmed"}],
216
- betas=["mcp-client-2025-11-20"]
217
- )
218
- ```
219
-
220
- Anthropic API の MCP コネクタは HTTP で公開されたリモートサーバーにしか接続できないため、先に Cloud Run へデプロイしておく必要があります。
221
- デプロイ方法は後述の「[Google Cloud Run へのデプロイ](#google-cloud-run-へのデプロイ)」、アクセストークンの取得方法は「[クライアント別の接続設定](#クライアント別の接続設定)」を参照してください。
222
-
223
- ---
224
-
225
- ### ソースからビルドする
226
-
227
- ソースコードを修正して使う場合は、リポジトリをクローンしてビルドします。
228
-
229
- ```bash
230
- git clone https://github.com/DJMIMA/PubMed_MCP.git
231
- cd PubMed_MCP
232
- npm install
233
- npm run build
234
- ```
235
-
236
- MCP クライアントの設定では、`npx -y @djmima/pubmed-mcp@latest` の代わりに `node /absolute/path/to/PubMed_MCP/dist/index.js` を指定します
237
- (Windows のパスは `C:/path/to/PubMed_MCP/dist/index.js` のようにスラッシュ区切りで書きます)。
238
- CLI として使う場合は、続けて `npm link` を実行すると `pubmed` と `pubmed-mcp` コマンドが登録されます(解除は `npm rm -g @djmima/pubmed-mcp`)。
239
-
240
- `dist/` はリポジトリに含まれません。`git pull` でソースを更新したら `npm run build` を再実行し、
241
- MCP クライアントを再起動してください。起動中のサーバーは再起動するまで古いビルドのまま動きます。
242
-
243
- ### ローカル開発
244
-
245
- ```bash
246
- # .env ファイルを作成してAPIキーを設定
247
- cp .env.example .env
248
- # .env を編集して NCBI_API_KEY を記入
249
-
250
- # stdio モードで起動(デフォルト)
251
- npm run dev
252
-
253
- # HTTP モードで起動(PORT を設定すると有効になる。OAuth の環境変数も必須)
254
- PORT=8080 npm run dev
255
- # → http://localhost:8080/mcp MCP エンドポイント(Bearer トークン必須)
256
- # → http://localhost:8080/health ヘルスチェック
257
- ```
258
-
259
- HTTP モードは起動時に `OAUTH_ISSUER`・`OAUTH_JWKS_URL`・`MCP_RESOURCE_URL` を読み込み、1 つでも未設定だと次のエラーで終了します。
260
-
261
- ```
262
- Fatal error: Error: Missing required environment variable: OAUTH_ISSUER
263
- ```
264
-
265
- `.env` にこれらの値を設定してから起動してください(各値の意味は「[環境変数](#環境変数)」を参照)。
266
- `/health` や 401 応答を確かめるだけなら URL 形式の仮の値でも起動できますが、`/mcp` を実際に呼び出すには
267
- `MCP_RESOURCE_URL` を `aud` として発行されたアクセストークンが必要です。認証なしでツールを試す場合は stdio モードを使ってください。
268
-
269
- ---
270
-
271
- ## CLI
272
-
273
- MCP サーバーと同じツールを、シェルから `pubmed` コマンドとして実行できます。出力は MCP ツールと同じ JSON で、
274
- 標準出力またはファイル(`-o`)に書き出されます。結果をファイルに保存して `jq` で必要なフィールドだけを取り出せば、
275
- Claude Code や Codex などのエージェントが大量のメタデータをコンテキストに読み込まずに済みます。
276
-
277
- たとえば `TP53 AND myelodysplastic syndromes[mh]` の上位 20 件のメタデータは約 108 KB ありますが、
278
- PMID・年・タイトルだけを取り出すと 3.2 KB(約 1/34)になります。全文は 1 本で数万文字あります。
279
-
280
- ### インストール
281
-
282
- ```bash
283
- npm install -g @djmima/pubmed-mcp
284
- ```
285
-
286
- `pubmed --help` が表示されれば完了です。
287
-
288
- NCBI API キーは環境変数 `NCBI_API_KEY` から読み込みます。シェルの設定ファイル(`~/.bashrc`、`~/.zshrc` など)に
289
- `export NCBI_API_KEY=your_api_key_here` を書くか、Windows では `setx NCBI_API_KEY your_api_key_here` を一度実行してから
290
- ターミナルを開き直してください。
291
-
292
- [ソースからビルドして](#ソースからビルドする) `npm link` で登録した場合は、環境変数がなければリポジトリ直下の `.env`
293
- (`cp .env.example .env` で作成)も読み込むため、どのディレクトリから実行してもキーが使われます。
294
-
295
- ### コマンド
296
-
297
- | コマンド | 対応する MCP ツール | 入力 | 1 リクエストの件数 |
298
- |---|---|---|---|
299
- | `pubmed search <query>` | `search_articles` | 検索式(`-` で標準入力から) | — |
300
- | `pubmed metadata <pmid>...` | `get_article_metadata` | PMID | 200 |
301
- | `pubmed related <pmid>...` | `find_related_articles` | PMID | 200 |
302
- | `pubmed citation --journal <雑誌> --year <年> ...` | `lookup_article_by_citation` | オプション、または `--input` で JSON 配列か JSON Lines | 100 |
303
- | `pubmed convert <id>...` | `convert_article_ids` | PMID・PMCID・DOI のいずれか(`--id-type` で指定) | 200 |
304
- | `pubmed fulltext <pmcid>...` | `get_full_text_article` | PMCID | 20 |
305
- | `pubmed copyright <pmid>...` | `get_copyright_status` | PMID | 50 |
306
-
307
- - オプション名は MCP のパラメータ名をケバブケースにしたものです(`max_results` → `--max-results`、`date_from` → `--date-from` など)。詳しくは `pubmed <command> --help` を参照してください。
308
- - ID は引数(空白またはカンマ区切り)で渡すか、`-` を指定して標準入力から 1 行 1 件で渡します。
309
- - 1 リクエストの件数を超える ID は自動で分割して順番に送り、結果を 1 つの JSON にまとめます。MCP ツールの件数上限(メタデータなら 200 件)は受けません。
310
- - `-o <file>` を付けると JSON をファイルに書き出し、件数とファイルサイズを標準エラー出力に表示します。
311
- - 終了コードは、成功で 0、NCBI やファイル操作のエラーで 1、使い方の誤りで 2 です。エラーの内容は標準エラー出力に出ます。
312
-
313
- ### 使用例
314
-
315
- ```bash
316
- # 検索して、PubMed が検索式をどう解釈したかを確認する
317
- pubmed search "TP53[tiab] AND myelodysplastic syndromes[mh]" --max-results 200 -o search.json
318
- jq '{total_count, query_translation, warnings, errors}' search.json
319
-
320
- # ヒットした全件のメタデータを取得し、PMID・年・雑誌・タイトルの一覧にする
321
- jq -r '.pmids[]' search.json | pubmed metadata - -o meta.json
322
- jq -r '.articles[] | [.identifiers.pmid, .publication_date.year, .journal.iso_abbreviation, .title] | @tsv' meta.json
323
-
324
- # 全文を取得できる論文だけ全文を取得し、必要な箇所だけを検索する
325
- jq -r '.pmids[]' search.json | pubmed copyright - -o rights.json
326
- jq -r '.statuses[] | select(.has_full_text) | .pmcid' rights.json | pubmed fulltext - -o full.json
327
- jq -r '.articles[].full_text' full.json | grep -n -i 'venetoclax'
328
- ```
329
-
330
- ### エージェントから使う
331
-
332
- [`skills/pubmed-cli/SKILL.md`](skills/pubmed-cli/SKILL.md) は、CLI の使い方(結果はファイルに保存して jq で読む、ID はまとめて 1 回で渡す、など)と
333
- jq のレシピをエージェントに教える Skill です。ユーザー単位のスキルフォルダにコピーすると、どのプロジェクトでも使われます。
334
- Claude Code(Claude Desktop の Code タブを含む)は `~/.claude/skills/`、Codex は `~/.agents/skills/` から読み込みます
335
- (Windows では `~` が `%USERPROFILE%` になります)。
336
-
337
- Skill は npm パッケージにも含まれているので、`npm install -g` でインストールした場合は次のコマンドでコピーできます
338
- (Windows では Git Bash で実行してください)。
339
-
340
- ```bash
341
- SKILL_DIR="$(npm root -g)/@djmima/pubmed-mcp/skills/pubmed-cli"
342
- mkdir -p ~/.claude/skills ~/.agents/skills
343
- cp -r "$SKILL_DIR" ~/.claude/skills/
344
- cp -r "$SKILL_DIR" ~/.agents/skills/
345
- ```
346
-
347
- ソースからビルドした場合は、リポジトリ直下で `SKILL_DIR=skills/pubmed-cli` としてから同じコマンドを実行します。
348
- パッケージや Skill を更新したときは、もう一度コピーしてください。
349
-
350
- Claude Code でコマンドを実行するたびに許可を求められないようにするには、`~/.claude/settings.json` の `permissions.allow` に
351
- `pubmed` と `jq` を追加します(既に設定がある場合は配列に追記してください)。
352
-
353
- ```json
354
- {
355
- "permissions": {
356
- "allow": ["Bash(pubmed:*)", "Bash(jq:*)"]
357
- }
358
- }
359
- ```
360
-
361
- 注意点:
362
-
363
- - CLI はシェルを実行できる環境(Claude Code、Codex など)でしか使えません。claude.ai のチャットや、Cloud Run にデプロイしたカスタムコネクタからは、これまでどおり MCP サーバーを使ってください。
364
- - NCBI のレート制限はプロセスごとにかかります。`pubmed` を並列で複数起動すると合計で上限を超えるため、ID はまとめて 1 回の実行で渡してください。
365
- - Windows では Git Bash か PowerShell 7 でパイプラインを実行してください。Windows PowerShell 5.1 の `>` はファイルを UTF-16 で保存するため、代わりに `-o` を使ってください。
366
-
367
- ---
368
-
369
- ## Google Cloud Run へのデプロイ
370
-
371
- このサーバーは Docker コンテナとして Google Cloud Run にデプロイできます。
372
- `PORT` 環境変数が設定されると自動的に HTTP モードで起動し、Streamable HTTP トランスポートで MCP クライアントと通信します。
373
-
374
- HTTP モードの `/mcp` は **OAuth 2.0 で保護** されています。サーバーは OAuth のリソースサーバーとして動作し、
375
- リクエストごとに Bearer トークン(JWT)の署名・発行者(`iss`)・対象(`aud`)・有効期限を、認可サーバーの公開鍵(JWKS)で検証します。
376
- ログイン画面やトークンの発行は外部の認可サーバーが担当します(ここでは WorkOS AuthKit を使います)。
377
-
378
- Cloud Run 自体は `--allow-unauthenticated` で公開します。Cloud Run の IAM 認証(IAP を含む)は claude.ai からの接続と両立しないため、
379
- アクセス制御は Cloud Run ではなくアプリ側の OAuth で行います。
380
-
381
- | パス | 認証 | 内容 |
382
- |------|------|------|
383
- | `/mcp` | Bearer トークン必須 | MCP エンドポイント |
384
- | `/.well-known/oauth-protected-resource` | 不要 | 認可サーバーの場所を示すメタデータ(RFC 9728) |
385
- | `/health` | 不要 | ヘルスチェック |
386
-
387
- ```
388
- Claude(Web / Desktop / モバイル)・Claude Code・Anthropic API
389
- │ Authorization: Bearer <WorkOS AuthKit が発行したアクセストークン>
390
- ▼
391
- Cloud Run(--allow-unauthenticated)
392
- │ /mcp で JWT を検証し、ALLOWED_SUBJECTS で利用者を限定
393
- ▼
394
- MCP ハンドラ
395
- ```
396
-
397
- Claude の各アプリと Claude Code は、トークンなしのリクエストに返る 401 応答(`WWW-Authenticate` ヘッダー)から
398
- メタデータをたどって認可サーバーを見つけ、ブラウザでのログインを経てトークンを取得します。
399
- Anthropic API から使う場合は、呼び出し側でトークンを用意して渡します。
400
-
401
- ### 前提条件
402
-
403
- - Google Cloud プロジェクト(課金が有効なもの)
404
- - [gcloud CLI](https://cloud.google.com/sdk/docs/install) インストール済み・ログイン済み
405
- - [WorkOS](https://workos.com/) アカウント(AuthKit を OAuth 認可サーバーとして使用)
406
-
407
- ### ステップ 1 — プロジェクトと API の準備
408
-
409
- ```bash
410
- # デプロイ先プロジェクトをセット
411
- gcloud config set project MY_PROJECT_ID
412
-
413
- # 必要な API を有効化(初回のみ)
414
- gcloud services enable \
415
- run.googleapis.com \
416
- artifactregistry.googleapis.com \
417
- cloudbuild.googleapis.com \
418
- secretmanager.googleapis.com # NCBI_API_KEY を管理する場合のみ
419
- ```
420
-
421
- ### ステップ 2 — Artifact Registry リポジトリを作成(初回のみ)
422
-
423
- ```bash
424
- gcloud artifacts repositories create pubmed-mcp \
425
- --repository-format=docker \
426
- --location=asia-northeast1 \
427
- --description="PubMed MCP Server"
428
- ```
429
-
430
- ### ステップ 3 — シークレットを Secret Manager に登録
431
-
432
- NCBI API キーをコマンド履歴やログに残さないために Secret Manager を使います。
433
-
434
- ```bash
435
- # NCBI API キーを登録
436
- echo -n "your_ncbi_api_key" | \
437
- gcloud secrets create ncbi-api-key \
438
- --data-file=- \
439
- --replication-policy=automatic
440
- ```
441
-
442
- ### ステップ 4 — コンテナイメージをビルドしてプッシュ
443
-
444
- Cloud Build を使うとローカルに Docker をインストールせずにビルドできます。
445
- `gcloud builds submit` はカレントディレクトリのファイルを Cloud Build に送信するため、
446
- **事前にリポジトリをクローンしておく必要があります。**
447
-
448
- ```bash
449
- # リポジトリをクローンしてディレクトリに移動
450
- git clone https://github.com/DJMIMA/PubMed_MCP.git
451
- cd PubMed_MCP
452
-
453
- # Cloud Build でビルド(ローカルに Docker 不要)
454
- gcloud builds submit \
455
- --region=asia-northeast1 \
456
- --tag asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:latest
457
- ```
458
-
459
- ローカルの Docker でビルドする場合:
460
-
461
- ```bash
462
- gcloud auth configure-docker asia-northeast1-docker.pkg.dev
463
-
464
- docker build -t asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:latest .
465
- docker push asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:latest
466
- ```
467
-
468
- ### ステップ 5 — OAuth 認可サーバー(WorkOS AuthKit)を設定
469
-
470
- まず MCP エンドポイントの公開 URL を決めます。Cloud Run のサービスには
471
- `https://サービス名-プロジェクト番号.リージョン.run.app` 形式の URL が割り当てられるため、デプロイ前に確定できます。
472
-
473
- ```bash
474
- PROJECT_NUMBER=$(gcloud projects describe MY_PROJECT_ID --format='value(projectNumber)')
475
- MCP_RESOURCE_URL="https://pubmed-mcp-${PROJECT_NUMBER}.asia-northeast1.run.app/mcp"
476
- echo "$MCP_RESOURCE_URL"
477
- ```
478
-
479
- この URL が `MCP_RESOURCE_URL` です。WorkOS の Resource Indicator や各クライアントに登録する URL とは**完全に同じ文字列**にしてください
480
- (クライアントは接続先の URL とメタデータの `resource` が一致するかを確認し、サーバーはトークンの `aud` をこの値と照合します)。
481
- Cloud Run には `https://pubmed-mcp-xxxx-an.a.run.app` 形式の URL も割り当てられ、どちらでもアクセスできますが、OAuth で使う URL はどちらか一方に統一します。
482
-
483
- 次に WorkOS ダッシュボードで AuthKit を設定します。
484
-
485
- 1. **Connect → Configuration** で **Dynamic Client Registration** を有効にする(claude.ai などの MCP クライアントが、自分自身を OAuth クライアントとして登録できるようにするため)
486
- 2. `MCP_RESOURCE_URL` を **Resource Indicator** として登録する(アクセストークンの `aud` がこの URL になる)
487
- 3. AuthKit ドメインの認可サーバーメタデータを開き、`issuer` と `jwks_uri` の値を控える
488
-
489
- ```bash
490
- curl https://YOUR_AUTHKIT_DOMAIN/.well-known/oauth-authorization-server
491
- # → {"issuer":"https://YOUR_AUTHKIT_DOMAIN", ..., "jwks_uri":"https://YOUR_AUTHKIT_DOMAIN/oauth2/jwks", ...}
492
- ```
493
-
494
- WorkOS の Staging 環境と Production 環境は、設定も AuthKit ドメインも別々です。環境を切り替えるときは上の設定をやり直し、
495
- `OAUTH_ISSUER`・`OAUTH_JWKS_URL` も差し替えてください。
496
-
497
- ### ステップ 6 — Cloud Run にデプロイ
498
-
499
- ステップ 5 で控えた値を使ってデプロイします。`ALLOWED_SUBJECTS` には最初は仮の値(ここでは `placeholder`)を入れておきます(理由はステップ 8 を参照)。
500
-
501
- ```bash
502
- # ステップ 5 で控えた issuer と jwks_uri の値に置き換える
503
- OAUTH_ISSUER="https://YOUR_AUTHKIT_DOMAIN"
504
- OAUTH_JWKS_URL="https://YOUR_AUTHKIT_DOMAIN/oauth2/jwks"
505
-
506
- gcloud run deploy pubmed-mcp \
507
- --image asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:latest \
508
- --region asia-northeast1 \
509
- --port 8080 \
510
- --memory 512Mi \
511
- --cpu 1 \
512
- --timeout 300 \
513
- --allow-unauthenticated \
514
- --set-secrets NCBI_API_KEY=ncbi-api-key:latest \
515
- --set-env-vars "OAUTH_ISSUER=${OAUTH_ISSUER},OAUTH_JWKS_URL=${OAUTH_JWKS_URL},MCP_RESOURCE_URL=${MCP_RESOURCE_URL},ALLOWED_SUBJECTS=placeholder"
516
- ```
517
-
518
- | 変数 | 設定する値 |
519
- |------|-----------|
520
- | `OAUTH_ISSUER` | 認可サーバーメタデータの `issuer`。トークンの `iss` と完全一致で照合されるので、末尾の `/` の有無も含めてそのままコピーする |
521
- | `OAUTH_JWKS_URL` | 同じメタデータの `jwks_uri`。トークンの署名検証に使う公開鍵の URL |
522
- | `MCP_RESOURCE_URL` | ステップ 5 で決めた MCP エンドポイントの URL。トークンの `aud` と照合し、`/.well-known/oauth-protected-resource` の `resource` としても公開される |
523
- | `ALLOWED_SUBJECTS` | 接続を許可するユーザー ID(トークンの `sub`)のカンマ区切りリスト。空にすると、AuthKit で有効なトークンを取得できるユーザー全員が接続できる |
524
-
525
- `OAUTH_ISSUER`・`OAUTH_JWKS_URL`・`MCP_RESOURCE_URL` のどれかが欠けていると、サーバーは起動時に
526
- `Missing required environment variable: ...` で終了し、デプロイは失敗します。失敗した場合は Cloud Run のログでこのメッセージを確認してください
527
- (Cloud Console で設定した場合は、変数名の前後に空白が入っていないかも確認してください)。
528
-
529
- > **`--set-env-vars` と `--update-env-vars` の違い**: `--set-env-vars` は既存の環境変数をすべて削除してから、指定した値を設定します(指定しなかった変数は消えます)。
530
- > デプロイ後に一部の値だけを追加・変更するときは、既存の値とマージされる `--update-env-vars` を使ってください(`gcloud run services update` でも使えます)。
531
-
532
- ### ステップ 7 — 動作確認
533
-
534
- `/health` と `/.well-known/oauth-protected-resource` は認証なしで応答します。`/mcp` はトークンなしでは **401 が返れば正常** です。
535
-
536
- **Linux / macOS (bash)**
537
-
538
- ```bash
539
- PROJECT_NUMBER=$(gcloud projects describe MY_PROJECT_ID --format='value(projectNumber)')
540
- CLOUD_RUN_URL="https://pubmed-mcp-${PROJECT_NUMBER}.asia-northeast1.run.app"
541
-
542
- # ヘルスチェック
543
- curl "$CLOUD_RUN_URL/health"
544
- # → ok
545
-
546
- # Protected Resource Metadata(resource が MCP_RESOURCE_URL、authorization_servers が OAUTH_ISSUER になっていることを確認)
547
- curl "$CLOUD_RUN_URL/.well-known/oauth-protected-resource"
548
- # → {"resource":"https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/mcp","authorization_servers":["https://YOUR_AUTHKIT_DOMAIN"],"bearer_methods_supported":["header"]}
549
-
550
- # MCP エンドポイント(トークンなしなので 401。-i でヘッダーも表示。ヒアドキュメントでクォート問題を回避)
551
- curl -i "$CLOUD_RUN_URL/mcp" \
552
- -X POST \
553
- -H "Content-Type: application/json" \
554
- -d @- <<'EOF'
555
- {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"0.0.1"}}}
556
- EOF
557
- ```
558
-
559
- `/mcp` の応答は次のようになります(抜粋。HTTP のバージョンやヘッダー名の大文字・小文字は環境によって異なります)。
560
-
561
- ```
562
- HTTP/2 401
563
- www-authenticate: Bearer resource_metadata="https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/.well-known/oauth-protected-resource", error="missing_token"
564
- content-type: application/json; charset=utf-8
565
-
566
- {"error":"missing_token"}
567
- ```
568
-
569
- MCP クライアントは `WWW-Authenticate` ヘッダーの `resource_metadata` をたどって認可サーバーを見つけます。
570
- トークンの検証に失敗した場合は `error="invalid_token"` の 401、トークンは有効でも `sub` が `ALLOWED_SUBJECTS` にない場合は 403(`{"error":"forbidden"}`)が返ります。
571
-
572
- **Windows(コマンドプロンプト)**
573
-
574
- ```cmd
575
- :: %CLOUD_RUN_URL% に URL を設定してから実行(PROJECT_NUMBER は自分のプロジェクト番号に置き換える)
576
- set CLOUD_RUN_URL=https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app
577
-
578
- curl "%CLOUD_RUN_URL%/health"
579
-
580
- curl "%CLOUD_RUN_URL%/.well-known/oauth-protected-resource"
581
-
582
- 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\"}}}"
583
- ```
584
-
585
- **Windows(PowerShell)**
586
-
587
- ```powershell
588
- $CLOUD_RUN_URL = "https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app"
589
-
590
- curl.exe "$CLOUD_RUN_URL/health"
591
-
592
- curl.exe "$CLOUD_RUN_URL/.well-known/oauth-protected-resource"
593
-
594
- curl.exe -i "$CLOUD_RUN_URL/mcp" -X POST -H "Content-Type: application/json" `
595
- -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"0.0.1"}}}'
596
- ```
597
-
598
- ### ステップ 8 — 自分のユーザー ID を ALLOWED_SUBJECTS に登録
599
-
600
- Dynamic Client Registration では誰でも OAuth クライアントを登録できるため、`ALLOWED_SUBJECTS` で接続できるユーザーを限定します
601
- (空のままだと AuthKit にログインできるユーザー全員が `/mcp` を使えます。AuthKit でサインアップを許可している場合は第三者も含まれます)。
602
- 設定する値はアクセストークンの `sub`(WorkOS のユーザー ID。`user_` で始まる値)です。
603
-
604
- `ALLOWED_SUBJECTS` が空のときは `sub` がログに記録されません。そのため、仮の値を入れた状態で一度接続し、拒否されたときのログから確認します。
605
-
606
- 1. 後述の「[クライアント別の接続設定](#クライアント別の接続設定)」の手順で claude.ai などから接続し、WorkOS AuthKit でログインします。
607
- `sub` が `ALLOWED_SUBJECTS` に含まれていないため接続は 403 で失敗し、サーバーのログに `[auth] forbidden subject: ...` が記録されます。
608
- 2. ログから自分の `sub` を確認します(WorkOS ダッシュボードの **Users** で確認できるユーザー ID と同じ値です)。
609
-
610
- ```bash
611
- gcloud run services logs read pubmed-mcp \
612
- --region asia-northeast1 \
613
- --log-filter='textPayload:"forbidden subject"' \
614
- --limit 5
615
- # → ... [auth] forbidden subject: user_01XXXXXXXXXXXXXXXXXXXXXXXX
616
- ```
617
-
618
- 3. その値を設定します。`--update-env-vars` なので、ほかの環境変数はそのまま残ります。
619
-
620
- ```bash
621
- gcloud run services update pubmed-mcp \
622
- --region asia-northeast1 \
623
- --update-env-vars ALLOWED_SUBJECTS=user_01XXXXXXXXXXXXXXXXXXXXXXXX
624
- ```
625
-
626
- 複数のユーザーを許可する場合は、値にカンマが含まれるため `^@^` で区切り文字を変えて指定します
627
- (例: `--update-env-vars '^@^ALLOWED_SUBJECTS=user_01AAAA,user_01BBBB'`。Windows の cmd / PowerShell では `^` を `^^^^` と書きます。詳しくは `gcloud topic escaping` を参照)。
628
- 4. クライアントから接続し直します。
629
-
630
- ---
631
-
632
- ### クライアント別の接続設定
633
-
634
- デプロイ後、各クライアントに MCP エンドポイントの URL を登録します。URL は `MCP_RESOURCE_URL` と**完全に同じ文字列**
635
- (例: `https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/mcp`)にしてください。
636
- 初回接続時に WorkOS AuthKit のログイン画面が開きます。ログインしたアカウントの `sub` が `ALLOWED_SUBJECTS` に含まれていれば接続できます
637
- (自分の `sub` を登録する手順はステップ 8 を参照)。
638
-
639
- **Claude(Web 版・Claude Desktop)— カスタムコネクタ**
640
-
641
- リモートサーバーは「カスタムコネクタ」として追加します([Claude ヘルプセンター](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp))。
642
- カスタムコネクタは Free プランでも 1 つまで利用できます。
643
-
644
- - Pro / Max プラン
645
- 1. [claude.ai](https://claude.ai) または Claude Desktop で **Customize > Connectors** を開く
646
- 2. **+** → **Add custom connector** をクリック
647
- 3. URL(`MCP_RESOURCE_URL`)を入力して **Add** をクリック
648
- 4. 追加したコネクタの **Connect** から認証する(WorkOS AuthKit のログイン画面が開く)
649
- - Team / Enterprise プラン
650
- 1. オーナーが **Organization settings > Connectors** を開き、**Add** → **Custom** にカーソルを合わせて **Web** を選ぶ
651
- 2. URL(`MCP_RESOURCE_URL`)を入力して **Add** をクリック
652
- 3. 各メンバーが **Customize > Connectors** でこのコネクタを探し、**Connect** から認証する
653
-
654
- **Advanced settings** の OAuth Client ID / OAuth Client Secret は空欄のままで構いません(Dynamic Client Registration でクライアントが自動登録されます)。
655
- チャットで使うときは、入力欄左下の **+** → **Connectors** から会話ごとに有効にします。
656
-
657
- Claude Desktop の `claude_desktop_config.json` はローカル(stdio)サーバー用の設定で、`"type"` や `"url"` を書いてもリモートサーバーには接続されません。
658
- Desktop でもリモートサーバーは上記のカスタムコネクタとして追加してください(Web 版で追加したコネクタは Desktop でもそのまま使えます)。
659
-
660
- **Claude モバイルアプリ(Android / iOS)**
661
-
662
- Web 版または Claude Desktop で追加したカスタムコネクタは、同じアカウントのモバイルアプリでもそのまま使えます。
663
- モバイルアプリからのコネクタ追加はベータ機能のため、追加は Web 版か Desktop で行ってください。
664
-
665
- **Claude Code**
666
-
667
- ```bash
668
- claude mcp add --transport http --scope user pubmed-remote https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/mcp
669
- ```
670
-
671
- Claude Code は `~/.claude/settings.json` の `mcpServers` を読み込まないため、`claude mcp add` で登録します。
672
- 登録後、Claude Code で `/mcp` を開いて `pubmed-remote` を選び、ブラウザで WorkOS AuthKit にログインして認証します。
673
-
674
- **Anthropic API(Python)**
675
-
676
- ```python
677
- import os
678
-
679
- import anthropic
680
-
681
- client = anthropic.Anthropic()
682
- response = client.beta.messages.create(
683
- model="claude-opus-5",
684
- max_tokens=16000,
685
- messages=[{"role": "user", "content": "..."}],
686
- mcp_servers=[{
687
- "type": "url",
688
- "url": "https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/mcp",
689
- "name": "pubmed",
690
- "authorization_token": os.environ["PUBMED_MCP_TOKEN"],
691
- }],
692
- tools=[{"type": "mcp_toolset", "mcp_server_name": "pubmed"}],
693
- betas=["mcp-client-2025-11-20"]
694
- )
695
- ```
696
-
697
- MCP コネクタは OAuth のログインもトークンの更新も行いません。WorkOS AuthKit がこのサーバー向けに発行したアクセストークン
698
- (`aud` が `MCP_RESOURCE_URL` で、`sub` が `ALLOWED_SUBJECTS` に含まれるもの)を呼び出し側で取得して `authorization_token` に渡し、
699
- 有効期限が切れたら取得し直してください。
700
-
701
- テスト用のトークンは MCP Inspector で取得できます([MCP connector ドキュメント](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector#authentication))。
702
-
703
- 1. `npx @modelcontextprotocol/inspector` を実行
704
- 2. **Transport type** で **Streamable HTTP** を選び、URL に `MCP_RESOURCE_URL` を入力
705
- 3. **Open Auth Settings** → **Quick OAuth Flow** をクリックし、WorkOS AuthKit でログイン
706
- 4. **Authentication complete** まで進めたら、表示された `access_token` の値をコピー
707
-
708
- ---
709
-
710
- ### イメージを更新する(再デプロイ)
711
-
712
- ```bash
713
- cd PubMed_MCP
714
- git pull
715
-
716
- gcloud builds submit \
717
- --region=asia-northeast1 \
718
- --tag asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:latest
719
-
720
- gcloud run services update pubmed-mcp \
721
- --region asia-northeast1 \
722
- --image asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:latest
723
- ```
724
-
725
- `--image` だけを指定した更新では、環境変数とシークレットの設定はそのまま引き継がれます。
726
-
727
- ---
728
-
729
- ## 開発コマンド
730
-
731
- ```bash
732
- npm run build # TypeScript をコンパイル → dist/
733
- npm run dev # tsx で直接実行(開発用)
734
- npm run cli -- search "CRISPR" --max-results 5 # CLI を tsx で直接実行(開発用)
735
- npm test # テストスイートを実行
736
- npm run typecheck # 型チェックのみ(コンパイルなし)
737
- npm run lint # ESLint を実行
738
- ```
739
-
740
- ### npm へのリリース(メンテナー向け)
741
-
742
- GitHub で Release を公開すると、[`.github/workflows/release.yml`](.github/workflows/release.yml) が npm に公開します。
743
- npm の [Trusted Publishing](https://docs.npmjs.com/trusted-publishers/)(OIDC)を使うため、npm のトークンはリポジトリに保存しません。
744
-
745
- 1. `npm version patch`(または `minor` / `major`)で `package.json` のバージョンを上げ、PR を作って main にマージする
746
- 2. GitHub で `v<バージョン>`(例: `v1.0.1`)のタグを付けた Release を公開する
747
- 3. ワークフローがタグと `package.json` のバージョンが一致するか確かめ、型チェック・lint・テスト・ビルドを通してから公開する
748
-
749
- 初回だけは手動で公開します。Trusted Publishing はパッケージが npm に存在しないと設定できないためです。
750
-
751
- 1. 2FA を有効にした npm アカウントで `npm login` し、main の最新を `npm publish` で公開する
752
- 2. npmjs.com のパッケージ設定 → **Trusted Publisher** で GitHub Actions を選び、リポジトリ `DJMIMA/PubMed_MCP`、ワークフロー `release.yml` を登録する
753
- 3. 同じ設定画面の **Publishing access** で、トークンによる公開を禁止する設定にする
754
-
755
- ---
756
-
757
- ## 環境変数
758
-
759
- | 変数 | 必須 | 説明 |
760
- |------|------|------|
761
- | `NCBI_API_KEY` | 任意 | NCBIのAPIキー。設定するとレートリミットが 3 → 8 req/s になる |
762
- | `PORT` | 任意 | 設定するとHTTPモードで起動。Cloud Run はこの変数を自動で注入する |
763
- | `OAUTH_ISSUER` | HTTPモードでは必須 | 認可サーバーの issuer(認可サーバーメタデータの `issuer`)。トークンの `iss` と照合する |
764
- | `OAUTH_JWKS_URL` | HTTPモードでは必須 | 認可サーバーの JWKS の URL(同じメタデータの `jwks_uri`)。トークンの署名検証に使う |
765
- | `MCP_RESOURCE_URL` | HTTPモードでは必須 | MCP エンドポイントの公開 URL。トークンの `aud` と照合し、Protected Resource Metadata の `resource` として公開する |
766
- | `ALLOWED_SUBJECTS` | 任意(推奨) | 接続を許可するトークン `sub` のカンマ区切りリスト。空なら有効なトークンを持つ全ユーザーを許可する |
767
-
768
- `OAUTH_ISSUER`・`OAUTH_JWKS_URL`・`MCP_RESOURCE_URL`・`ALLOWED_SUBJECTS` は HTTP モードでのみ使われ、stdio モードでは不要です。
769
- 値の決め方は「[Google Cloud Run へのデプロイ](#google-cloud-run-へのデプロイ)」のステップ 5〜8 を参照してください。
770
-
771
- `.env` ファイルを使う場合は `.env.example` をコピーして編集してください。
772
- 本番環境では、`NCBI_API_KEY` は Cloud Run の `--set-secrets` で Secret Manager から渡し、OAuth 関連の値は `--set-env-vars` / `--update-env-vars` で設定してください。
773
-
774
- ---
775
-
776
- ## 参考リンク
777
-
778
- - [MCP ドキュメント](https://modelcontextprotocol.io/docs)
779
- - [NCBI E-utilities ドキュメント](https://www.ncbi.nlm.nih.gov/books/NBK25499/)
780
- - [NCBI APIキー登録](https://www.ncbi.nlm.nih.gov/account/)
781
- - [PubMed 検索ヘルプ](https://pubmed.ncbi.nlm.nih.gov/help/)
782
- - [Cloud Run ドキュメント](https://cloud.google.com/run/docs)
783
- - [Secret Manager ドキュメント](https://cloud.google.com/secret-manager/docs)
784
-
785
- ---
786
-
787
- ## ライセンス
788
-
789
- このリポジトリのソースコードは [MIT License](LICENSE) で公開しています。
790
-
791
- - このプロジェクトは NCBI / NLM(米国国立医学図書館)とは関係のない非公式のツールです。
792
- - PubMed / PMC へのアクセスには NCBI の E-utilities を使います。利用時は [NCBI の利用ポリシー](https://www.ncbi.nlm.nih.gov/home/about/policies/) と [E-utilities の利用ガイドライン](https://www.ncbi.nlm.nih.gov/books/NBK25497/) に従ってください。
793
- - MIT License が適用されるのはこのソフトウェアのコードだけです。取得した抄録や全文の著作権は各著者・出版社に帰属します。論文ごとのライセンスは `get_copyright_status` で確認できます。
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`.