@djmima/pubmed-mcp 1.0.1 → 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 +8 -1
- package/README.md +349 -326
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.0.2] - 2026-11-03
|
|
9
|
+
|
|
10
|
+
### 変更
|
|
11
|
+
|
|
12
|
+
- README を英語にしました。日本語版は `README_ja.md` に移し、両方の冒頭に npm のバージョン・パッケージサイズ・ライセンス・Node.js のバージョン・CI のバッジと、言語の切り替えリンクを付けました。
|
|
13
|
+
|
|
8
14
|
## [1.0.1] - 2026-11-01
|
|
9
15
|
|
|
10
16
|
### 変更
|
|
@@ -23,6 +29,7 @@ npm パッケージ [`@djmima/pubmed-mcp`](https://www.npmjs.com/package/@djmima
|
|
|
23
29
|
- 同じツールをシェルから実行する `pubmed` CLI と、エージェント向けの Skill(`skills/pubmed-cli`)
|
|
24
30
|
- MIT License
|
|
25
31
|
|
|
26
|
-
[Unreleased]: https://github.com/DJMIMA/PubMed_MCP/compare/v1.0.
|
|
32
|
+
[Unreleased]: https://github.com/DJMIMA/PubMed_MCP/compare/v1.0.2...HEAD
|
|
33
|
+
[1.0.2]: https://github.com/DJMIMA/PubMed_MCP/compare/v1.0.1...v1.0.2
|
|
27
34
|
[1.0.1]: https://github.com/DJMIMA/PubMed_MCP/compare/v1.0.0...v1.0.1
|
|
28
35
|
[1.0.0]: https://github.com/DJMIMA/PubMed_MCP/releases/tag/v1.0.0
|
package/README.md
CHANGED
|
@@ -1,44 +1,67 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">PubMed MCP</h1>
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
4
27
|
|
|
5
28
|
## Tools
|
|
6
29
|
|
|
7
|
-
| Tool |
|
|
8
|
-
|
|
9
|
-
| `search_articles` | PubMed
|
|
10
|
-
| `get_article_metadata` |
|
|
11
|
-
| `find_related_articles` | ELink
|
|
12
|
-
| `lookup_article_by_citation` |
|
|
13
|
-
| `convert_article_ids` | PMID
|
|
14
|
-
| `get_full_text_article` | PMC Open Access
|
|
15
|
-
| `get_copyright_status` |
|
|
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) |
|
|
16
39
|
|
|
17
|
-
|
|
40
|
+
The same tools are also available from the shell as the `pubmed` command (see [CLI](#cli)).
|
|
18
41
|
|
|
19
42
|
---
|
|
20
43
|
|
|
21
|
-
## `search_articles`
|
|
44
|
+
## ATM diagnostics and field restriction in `search_articles`
|
|
22
45
|
|
|
23
|
-
PubMed
|
|
24
|
-
|
|
25
|
-
|
|
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.
|
|
26
49
|
|
|
27
|
-
`search_articles`
|
|
28
|
-
|
|
50
|
+
`search_articles` accepts an optional `field` value and restricts every untagged term in the query
|
|
51
|
+
to that field:
|
|
29
52
|
|
|
30
53
|
- `title`
|
|
31
|
-
- `tiab
|
|
54
|
+
- `tiab` (Title/Abstract)
|
|
32
55
|
- `mesh`
|
|
33
56
|
- `author`
|
|
34
57
|
- `journal`
|
|
35
58
|
- `affiliation`
|
|
36
59
|
|
|
37
|
-
|
|
38
|
-
`field`
|
|
39
|
-
|
|
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.
|
|
40
63
|
|
|
41
|
-
|
|
64
|
+
The response always includes the following fields, normalized from the NCBI ESearch diagnostics:
|
|
42
65
|
|
|
43
66
|
```json
|
|
44
67
|
{
|
|
@@ -54,68 +77,68 @@ PubMed はタグのない検索語に Automatic Term Mapping(ATM)を適用
|
|
|
54
77
|
}
|
|
55
78
|
```
|
|
56
79
|
|
|
57
|
-
|
|
58
|
-
MCP
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
`[ti:~0]`
|
|
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]`.
|
|
63
86
|
|
|
64
87
|
---
|
|
65
88
|
|
|
66
|
-
##
|
|
89
|
+
## Requirements
|
|
67
90
|
|
|
68
|
-
- **Node.js** 22
|
|
69
|
-
- **NCBI API
|
|
70
|
-
- **jq
|
|
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)
|
|
71
94
|
|
|
72
95
|
---
|
|
73
96
|
|
|
74
|
-
##
|
|
97
|
+
## Installation
|
|
75
98
|
|
|
76
|
-
npm
|
|
99
|
+
The server is published on npm as [`@djmima/pubmed-mcp`](https://www.npmjs.com/package/@djmima/pubmed-mcp).
|
|
77
100
|
|
|
78
|
-
- **
|
|
79
|
-
|
|
80
|
-
- **[CLI](#cli)
|
|
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:
|
|
81
104
|
|
|
82
105
|
```bash
|
|
83
106
|
npm install -g @djmima/pubmed-mcp
|
|
84
107
|
```
|
|
85
108
|
|
|
86
|
-
`pubmed`
|
|
87
|
-
|
|
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`.
|
|
88
111
|
|
|
89
|
-
|
|
112
|
+
If you want to modify the source code, see [Building from source](#building-from-source).
|
|
90
113
|
|
|
91
|
-
### NCBI API
|
|
114
|
+
### Setting up an NCBI API key (recommended)
|
|
92
115
|
|
|
93
|
-
API
|
|
116
|
+
With an API key, the rate limit goes from 3 to 8 req/s.
|
|
94
117
|
|
|
95
|
-
1. [NCBI
|
|
96
|
-
2.
|
|
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)
|
|
97
120
|
|
|
98
|
-
MCP
|
|
99
|
-
`.env`
|
|
100
|
-
|
|
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.
|
|
101
124
|
|
|
102
|
-
API
|
|
103
|
-
|
|
104
|
-
`api_key`
|
|
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>`.
|
|
105
128
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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.
|
|
111
134
|
|
|
112
135
|
---
|
|
113
136
|
|
|
114
|
-
##
|
|
137
|
+
## Usage
|
|
115
138
|
|
|
116
|
-
### Claude Desktop
|
|
139
|
+
### Claude Desktop (local, stdio)
|
|
117
140
|
|
|
118
|
-
|
|
141
|
+
Add the server to `mcpServers` in `claude_desktop_config.json`.
|
|
119
142
|
|
|
120
143
|
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
121
144
|
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
@@ -134,39 +157,39 @@ APIキーは、このサーバーを運用する個人または組織が所有
|
|
|
134
157
|
}
|
|
135
158
|
```
|
|
136
159
|
|
|
137
|
-
-
|
|
138
|
-
- Windows
|
|
139
|
-
- `@latest
|
|
140
|
-
-
|
|
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.
|
|
141
164
|
|
|
142
|
-
|
|
143
|
-
|
|
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.
|
|
144
167
|
|
|
145
|
-
|
|
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`).
|
|
146
169
|
|
|
147
170
|
---
|
|
148
171
|
|
|
149
|
-
### Claude Code
|
|
172
|
+
### Claude Code (local, stdio)
|
|
150
173
|
|
|
151
|
-
`claude mcp add
|
|
174
|
+
Register the server with `claude mcp add`. With `--scope user`, it is available in every project.
|
|
152
175
|
|
|
153
176
|
```bash
|
|
154
177
|
claude mcp add --env NCBI_API_KEY=your_api_key_here --transport stdio --scope user pubmed-local -- npx -y @djmima/pubmed-mcp@latest
|
|
155
178
|
```
|
|
156
179
|
|
|
157
|
-
- `--`
|
|
158
|
-
- Windows
|
|
159
|
-
- `--env
|
|
160
|
-
-
|
|
161
|
-
-
|
|
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.
|
|
162
185
|
|
|
163
|
-
|
|
186
|
+
After registering, check the connection with `claude mcp list` or `/mcp` inside Claude Code.
|
|
164
187
|
|
|
165
188
|
---
|
|
166
189
|
|
|
167
|
-
### Codex
|
|
190
|
+
### Codex (local, stdio)
|
|
168
191
|
|
|
169
|
-
Codex
|
|
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:
|
|
170
193
|
|
|
171
194
|
```toml
|
|
172
195
|
[mcp_servers.pubmed-local]
|
|
@@ -177,20 +200,20 @@ args = ["-y", "@djmima/pubmed-mcp@latest"]
|
|
|
177
200
|
NCBI_API_KEY = "your_api_key_here"
|
|
178
201
|
```
|
|
179
202
|
|
|
180
|
-
- Windows
|
|
181
|
-
- Codex
|
|
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.
|
|
182
205
|
|
|
183
|
-
CLI
|
|
206
|
+
You can also register it from the CLI with `codex mcp add`:
|
|
184
207
|
|
|
185
208
|
```bash
|
|
186
209
|
codex mcp add pubmed-local --env NCBI_API_KEY=your_api_key_here -- npx -y @djmima/pubmed-mcp@latest
|
|
187
210
|
```
|
|
188
211
|
|
|
189
|
-
|
|
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.
|
|
190
213
|
|
|
191
214
|
---
|
|
192
215
|
|
|
193
|
-
### Anthropic API
|
|
216
|
+
### Anthropic API (programmatic use)
|
|
194
217
|
|
|
195
218
|
```python
|
|
196
219
|
import os
|
|
@@ -209,7 +232,7 @@ response = client.beta.messages.create(
|
|
|
209
232
|
"type": "url",
|
|
210
233
|
"url": "https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/mcp",
|
|
211
234
|
"name": "pubmed",
|
|
212
|
-
# /mcp
|
|
235
|
+
# /mcp is protected by OAuth, so an access token is required
|
|
213
236
|
"authorization_token": os.environ["PUBMED_MCP_TOKEN"],
|
|
214
237
|
}],
|
|
215
238
|
tools=[{"type": "mcp_toolset", "mcp_server_name": "pubmed"}],
|
|
@@ -217,14 +240,14 @@ response = client.beta.messages.create(
|
|
|
217
240
|
)
|
|
218
241
|
```
|
|
219
242
|
|
|
220
|
-
Anthropic API
|
|
221
|
-
|
|
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.
|
|
222
245
|
|
|
223
246
|
---
|
|
224
247
|
|
|
225
|
-
###
|
|
248
|
+
### Building from source
|
|
226
249
|
|
|
227
|
-
|
|
250
|
+
To modify the source code, clone the repository and build it.
|
|
228
251
|
|
|
229
252
|
```bash
|
|
230
253
|
git clone https://github.com/DJMIMA/PubMed_MCP.git
|
|
@@ -233,109 +256,109 @@ npm install
|
|
|
233
256
|
npm run build
|
|
234
257
|
```
|
|
235
258
|
|
|
236
|
-
MCP
|
|
237
|
-
|
|
238
|
-
CLI
|
|
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`).
|
|
239
262
|
|
|
240
|
-
`dist/`
|
|
241
|
-
MCP
|
|
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.
|
|
242
265
|
|
|
243
|
-
###
|
|
266
|
+
### Local development
|
|
244
267
|
|
|
245
268
|
```bash
|
|
246
|
-
# .env
|
|
269
|
+
# Create a .env file and set your API key
|
|
247
270
|
cp .env.example .env
|
|
248
|
-
# .env
|
|
271
|
+
# Edit .env and fill in NCBI_API_KEY
|
|
249
272
|
|
|
250
|
-
# stdio
|
|
273
|
+
# Start in stdio mode (default)
|
|
251
274
|
npm run dev
|
|
252
275
|
|
|
253
|
-
# HTTP
|
|
276
|
+
# Start in HTTP mode (enabled by setting PORT; the OAuth variables are also required)
|
|
254
277
|
PORT=8080 npm run dev
|
|
255
|
-
# → http://localhost:8080/mcp MCP
|
|
256
|
-
# → http://localhost:8080/health
|
|
278
|
+
# → http://localhost:8080/mcp MCP endpoint (Bearer token required)
|
|
279
|
+
# → http://localhost:8080/health Health check
|
|
257
280
|
```
|
|
258
281
|
|
|
259
|
-
HTTP
|
|
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:
|
|
260
283
|
|
|
261
284
|
```
|
|
262
285
|
Fatal error: Error: Missing required environment variable: OAUTH_ISSUER
|
|
263
286
|
```
|
|
264
287
|
|
|
265
|
-
`.env`
|
|
266
|
-
`/health`
|
|
267
|
-
`MCP_RESOURCE_URL`
|
|
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.
|
|
268
291
|
|
|
269
292
|
---
|
|
270
293
|
|
|
271
294
|
## CLI
|
|
272
295
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
Claude Code
|
|
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.
|
|
276
299
|
|
|
277
|
-
|
|
278
|
-
PMID
|
|
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.
|
|
279
302
|
|
|
280
|
-
###
|
|
303
|
+
### Installation
|
|
281
304
|
|
|
282
305
|
```bash
|
|
283
306
|
npm install -g @djmima/pubmed-mcp
|
|
284
307
|
```
|
|
285
308
|
|
|
286
|
-
`pubmed --help`
|
|
309
|
+
You're done when `pubmed --help` prints the help.
|
|
287
310
|
|
|
288
|
-
NCBI API
|
|
289
|
-
`export NCBI_API_KEY=your_api_key_here`
|
|
290
|
-
|
|
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.
|
|
291
314
|
|
|
292
|
-
[
|
|
293
|
-
|
|
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.
|
|
294
317
|
|
|
295
|
-
###
|
|
318
|
+
### Commands
|
|
296
319
|
|
|
297
|
-
|
|
|
320
|
+
| Command | MCP tool | Input | Items per request |
|
|
298
321
|
|---|---|---|---|
|
|
299
|
-
| `pubmed search <query>` | `search_articles` |
|
|
300
|
-
| `pubmed metadata <pmid>...` | `get_article_metadata` |
|
|
301
|
-
| `pubmed related <pmid>...` | `find_related_articles` |
|
|
302
|
-
| `pubmed citation --journal
|
|
303
|
-
| `pubmed convert <id>...` | `convert_article_ids` |
|
|
304
|
-
| `pubmed fulltext <pmcid>...` | `get_full_text_article` |
|
|
305
|
-
| `pubmed copyright <pmid>...` | `get_copyright_status` |
|
|
306
|
-
|
|
307
|
-
-
|
|
308
|
-
-
|
|
309
|
-
-
|
|
310
|
-
- `-o <file
|
|
311
|
-
-
|
|
312
|
-
|
|
313
|
-
###
|
|
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
|
|
314
337
|
|
|
315
338
|
```bash
|
|
316
|
-
#
|
|
339
|
+
# Search, and check how PubMed interpreted the query
|
|
317
340
|
pubmed search "TP53[tiab] AND myelodysplastic syndromes[mh]" --max-results 200 -o search.json
|
|
318
341
|
jq '{total_count, query_translation, warnings, errors}' search.json
|
|
319
342
|
|
|
320
|
-
#
|
|
343
|
+
# Get metadata for every hit and list PMID, year, journal, and title
|
|
321
344
|
jq -r '.pmids[]' search.json | pubmed metadata - -o meta.json
|
|
322
345
|
jq -r '.articles[] | [.identifiers.pmid, .publication_date.year, .journal.iso_abbreviation, .title] | @tsv' meta.json
|
|
323
346
|
|
|
324
|
-
#
|
|
347
|
+
# Get full text only for articles that have it, and search just the parts you need
|
|
325
348
|
jq -r '.pmids[]' search.json | pubmed copyright - -o rights.json
|
|
326
349
|
jq -r '.statuses[] | select(.has_full_text) | .pmcid' rights.json | pubmed fulltext - -o full.json
|
|
327
350
|
jq -r '.articles[].full_text' full.json | grep -n -i 'venetoclax'
|
|
328
351
|
```
|
|
329
352
|
|
|
330
|
-
###
|
|
353
|
+
### Using with agents
|
|
331
354
|
|
|
332
|
-
[`skills/pubmed-cli/SKILL.md`](skills/pubmed-cli/SKILL.md)
|
|
333
|
-
jq
|
|
334
|
-
Claude Code
|
|
335
|
-
|
|
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%`).
|
|
336
359
|
|
|
337
|
-
Skill
|
|
338
|
-
|
|
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):
|
|
339
362
|
|
|
340
363
|
```bash
|
|
341
364
|
SKILL_DIR="$(npm root -g)/@djmima/pubmed-mcp/skills/pubmed-cli"
|
|
@@ -344,11 +367,11 @@ cp -r "$SKILL_DIR" ~/.claude/skills/
|
|
|
344
367
|
cp -r "$SKILL_DIR" ~/.agents/skills/
|
|
345
368
|
```
|
|
346
369
|
|
|
347
|
-
|
|
348
|
-
|
|
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.
|
|
349
372
|
|
|
350
|
-
Claude Code
|
|
351
|
-
`
|
|
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):
|
|
352
375
|
|
|
353
376
|
```json
|
|
354
377
|
{
|
|
@@ -358,67 +381,67 @@ Claude Code でコマンドを実行するたびに許可を求められない
|
|
|
358
381
|
}
|
|
359
382
|
```
|
|
360
383
|
|
|
361
|
-
|
|
384
|
+
Notes:
|
|
362
385
|
|
|
363
|
-
- CLI
|
|
364
|
-
- NCBI
|
|
365
|
-
- Windows
|
|
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.
|
|
366
389
|
|
|
367
390
|
---
|
|
368
391
|
|
|
369
|
-
## Google Cloud Run
|
|
392
|
+
## Deploying to Google Cloud Run
|
|
370
393
|
|
|
371
|
-
|
|
372
|
-
`PORT`
|
|
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.
|
|
373
396
|
|
|
374
|
-
HTTP
|
|
375
|
-
|
|
376
|
-
|
|
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).
|
|
377
400
|
|
|
378
|
-
Cloud Run
|
|
379
|
-
|
|
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.
|
|
380
403
|
|
|
381
|
-
|
|
|
382
|
-
|
|
383
|
-
| `/mcp` | Bearer
|
|
384
|
-
| `/.well-known/oauth-protected-resource` |
|
|
385
|
-
| `/health` |
|
|
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 |
|
|
386
409
|
|
|
387
410
|
```
|
|
388
|
-
Claude
|
|
389
|
-
│ Authorization: Bearer <WorkOS AuthKit
|
|
411
|
+
Claude (Web / Desktop / mobile), Claude Code, Anthropic API
|
|
412
|
+
│ Authorization: Bearer <access token issued by WorkOS AuthKit>
|
|
390
413
|
▼
|
|
391
|
-
Cloud Run
|
|
392
|
-
│ /mcp
|
|
414
|
+
Cloud Run (--allow-unauthenticated)
|
|
415
|
+
│ /mcp verifies the JWT and limits users with ALLOWED_SUBJECTS
|
|
393
416
|
▼
|
|
394
|
-
MCP
|
|
417
|
+
MCP handler
|
|
395
418
|
```
|
|
396
419
|
|
|
397
|
-
Claude
|
|
398
|
-
|
|
399
|
-
Anthropic API
|
|
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.
|
|
400
423
|
|
|
401
|
-
###
|
|
424
|
+
### Prerequisites
|
|
402
425
|
|
|
403
|
-
- Google Cloud
|
|
404
|
-
- [gcloud CLI](https://cloud.google.com/sdk/docs/install)
|
|
405
|
-
- [WorkOS](https://workos.com/)
|
|
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)
|
|
406
429
|
|
|
407
|
-
###
|
|
430
|
+
### Step 1 — Set up the project and APIs
|
|
408
431
|
|
|
409
432
|
```bash
|
|
410
|
-
#
|
|
433
|
+
# Set the project to deploy to
|
|
411
434
|
gcloud config set project MY_PROJECT_ID
|
|
412
435
|
|
|
413
|
-
#
|
|
436
|
+
# Enable the required APIs (first time only)
|
|
414
437
|
gcloud services enable \
|
|
415
438
|
run.googleapis.com \
|
|
416
439
|
artifactregistry.googleapis.com \
|
|
417
440
|
cloudbuild.googleapis.com \
|
|
418
|
-
secretmanager.googleapis.com # NCBI_API_KEY
|
|
441
|
+
secretmanager.googleapis.com # only if you manage NCBI_API_KEY there
|
|
419
442
|
```
|
|
420
443
|
|
|
421
|
-
###
|
|
444
|
+
### Step 2 — Create an Artifact Registry repository (first time only)
|
|
422
445
|
|
|
423
446
|
```bash
|
|
424
447
|
gcloud artifacts repositories create pubmed-mcp \
|
|
@@ -427,36 +450,36 @@ gcloud artifacts repositories create pubmed-mcp \
|
|
|
427
450
|
--description="PubMed MCP Server"
|
|
428
451
|
```
|
|
429
452
|
|
|
430
|
-
###
|
|
453
|
+
### Step 3 — Store secrets in Secret Manager
|
|
431
454
|
|
|
432
|
-
NCBI API
|
|
455
|
+
Use Secret Manager so the NCBI API key doesn't end up in your command history or logs.
|
|
433
456
|
|
|
434
457
|
```bash
|
|
435
|
-
# NCBI API
|
|
458
|
+
# Store the NCBI API key
|
|
436
459
|
echo -n "your_ncbi_api_key" | \
|
|
437
460
|
gcloud secrets create ncbi-api-key \
|
|
438
461
|
--data-file=- \
|
|
439
462
|
--replication-policy=automatic
|
|
440
463
|
```
|
|
441
464
|
|
|
442
|
-
###
|
|
465
|
+
### Step 4 — Build and push the container image
|
|
443
466
|
|
|
444
|
-
Cloud Build
|
|
445
|
-
`gcloud builds submit`
|
|
446
|
-
|
|
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.**
|
|
447
470
|
|
|
448
471
|
```bash
|
|
449
|
-
#
|
|
472
|
+
# Clone the repository and move into it
|
|
450
473
|
git clone https://github.com/DJMIMA/PubMed_MCP.git
|
|
451
474
|
cd PubMed_MCP
|
|
452
475
|
|
|
453
|
-
# Cloud Build
|
|
476
|
+
# Build with Cloud Build (no local Docker needed)
|
|
454
477
|
gcloud builds submit \
|
|
455
478
|
--region=asia-northeast1 \
|
|
456
479
|
--tag asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:latest
|
|
457
480
|
```
|
|
458
481
|
|
|
459
|
-
|
|
482
|
+
To build with local Docker:
|
|
460
483
|
|
|
461
484
|
```bash
|
|
462
485
|
gcloud auth configure-docker asia-northeast1-docker.pkg.dev
|
|
@@ -465,10 +488,10 @@ docker build -t asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:l
|
|
|
465
488
|
docker push asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:latest
|
|
466
489
|
```
|
|
467
490
|
|
|
468
|
-
###
|
|
491
|
+
### Step 5 — Set up the OAuth authorization server (WorkOS AuthKit)
|
|
469
492
|
|
|
470
|
-
|
|
471
|
-
`https
|
|
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.
|
|
472
495
|
|
|
473
496
|
```bash
|
|
474
497
|
PROJECT_NUMBER=$(gcloud projects describe MY_PROJECT_ID --format='value(projectNumber)')
|
|
@@ -476,30 +499,30 @@ MCP_RESOURCE_URL="https://pubmed-mcp-${PROJECT_NUMBER}.asia-northeast1.run.app/m
|
|
|
476
499
|
echo "$MCP_RESOURCE_URL"
|
|
477
500
|
```
|
|
478
501
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
Cloud Run
|
|
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.
|
|
482
505
|
|
|
483
|
-
|
|
506
|
+
Next, configure AuthKit in the WorkOS dashboard.
|
|
484
507
|
|
|
485
|
-
1. **Connect → Configuration
|
|
486
|
-
2. `MCP_RESOURCE_URL`
|
|
487
|
-
3. AuthKit
|
|
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
|
|
488
511
|
|
|
489
512
|
```bash
|
|
490
513
|
curl https://YOUR_AUTHKIT_DOMAIN/.well-known/oauth-authorization-server
|
|
491
514
|
# → {"issuer":"https://YOUR_AUTHKIT_DOMAIN", ..., "jwks_uri":"https://YOUR_AUTHKIT_DOMAIN/oauth2/jwks", ...}
|
|
492
515
|
```
|
|
493
516
|
|
|
494
|
-
WorkOS
|
|
495
|
-
`OAUTH_ISSUER
|
|
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`.
|
|
496
519
|
|
|
497
|
-
###
|
|
520
|
+
### Step 6 — Deploy to Cloud Run
|
|
498
521
|
|
|
499
|
-
|
|
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).
|
|
500
523
|
|
|
501
524
|
```bash
|
|
502
|
-
#
|
|
525
|
+
# Replace with the issuer and jwks_uri values from step 5
|
|
503
526
|
OAUTH_ISSUER="https://YOUR_AUTHKIT_DOMAIN"
|
|
504
527
|
OAUTH_JWKS_URL="https://YOUR_AUTHKIT_DOMAIN/oauth2/jwks"
|
|
505
528
|
|
|
@@ -515,23 +538,23 @@ gcloud run deploy pubmed-mcp \
|
|
|
515
538
|
--set-env-vars "OAUTH_ISSUER=${OAUTH_ISSUER},OAUTH_JWKS_URL=${OAUTH_JWKS_URL},MCP_RESOURCE_URL=${MCP_RESOURCE_URL},ALLOWED_SUBJECTS=placeholder"
|
|
516
539
|
```
|
|
517
540
|
|
|
518
|
-
|
|
|
519
|
-
|
|
520
|
-
| `OAUTH_ISSUER` |
|
|
521
|
-
| `OAUTH_JWKS_URL` |
|
|
522
|
-
| `MCP_RESOURCE_URL` |
|
|
523
|
-
| `ALLOWED_SUBJECTS` |
|
|
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 |
|
|
524
547
|
|
|
525
|
-
`OAUTH_ISSUER
|
|
526
|
-
`Missing required environment variable: ...`
|
|
527
|
-
|
|
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).
|
|
528
551
|
|
|
529
|
-
> **`--set-env-vars`
|
|
530
|
-
>
|
|
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`).
|
|
531
554
|
|
|
532
|
-
###
|
|
555
|
+
### Step 7 — Verify the deployment
|
|
533
556
|
|
|
534
|
-
`/health`
|
|
557
|
+
`/health` and `/.well-known/oauth-protected-resource` respond without authentication. Without a token, `/mcp` **should return 401**.
|
|
535
558
|
|
|
536
559
|
**Linux / macOS (bash)**
|
|
537
560
|
|
|
@@ -539,15 +562,15 @@ gcloud run deploy pubmed-mcp \
|
|
|
539
562
|
PROJECT_NUMBER=$(gcloud projects describe MY_PROJECT_ID --format='value(projectNumber)')
|
|
540
563
|
CLOUD_RUN_URL="https://pubmed-mcp-${PROJECT_NUMBER}.asia-northeast1.run.app"
|
|
541
564
|
|
|
542
|
-
#
|
|
565
|
+
# Health check
|
|
543
566
|
curl "$CLOUD_RUN_URL/health"
|
|
544
567
|
# → ok
|
|
545
568
|
|
|
546
|
-
# Protected Resource Metadata
|
|
569
|
+
# Protected Resource Metadata (check that resource is MCP_RESOURCE_URL and authorization_servers is OAUTH_ISSUER)
|
|
547
570
|
curl "$CLOUD_RUN_URL/.well-known/oauth-protected-resource"
|
|
548
571
|
# → {"resource":"https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/mcp","authorization_servers":["https://YOUR_AUTHKIT_DOMAIN"],"bearer_methods_supported":["header"]}
|
|
549
572
|
|
|
550
|
-
# MCP
|
|
573
|
+
# MCP endpoint (401 because there is no token; -i shows headers; a heredoc avoids quoting problems)
|
|
551
574
|
curl -i "$CLOUD_RUN_URL/mcp" \
|
|
552
575
|
-X POST \
|
|
553
576
|
-H "Content-Type: application/json" \
|
|
@@ -556,7 +579,7 @@ curl -i "$CLOUD_RUN_URL/mcp" \
|
|
|
556
579
|
EOF
|
|
557
580
|
```
|
|
558
581
|
|
|
559
|
-
`/mcp`
|
|
582
|
+
The `/mcp` response looks like this (excerpt; the HTTP version and header-name casing vary by environment):
|
|
560
583
|
|
|
561
584
|
```
|
|
562
585
|
HTTP/2 401
|
|
@@ -566,13 +589,13 @@ content-type: application/json; charset=utf-8
|
|
|
566
589
|
{"error":"missing_token"}
|
|
567
590
|
```
|
|
568
591
|
|
|
569
|
-
MCP
|
|
570
|
-
|
|
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"}`).
|
|
571
594
|
|
|
572
|
-
**Windows
|
|
595
|
+
**Windows (Command Prompt)**
|
|
573
596
|
|
|
574
597
|
```cmd
|
|
575
|
-
:: %CLOUD_RUN_URL%
|
|
598
|
+
:: Set %CLOUD_RUN_URL% before running (replace PROJECT_NUMBER with your project number)
|
|
576
599
|
set CLOUD_RUN_URL=https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app
|
|
577
600
|
|
|
578
601
|
curl "%CLOUD_RUN_URL%/health"
|
|
@@ -582,7 +605,7 @@ curl "%CLOUD_RUN_URL%/.well-known/oauth-protected-resource"
|
|
|
582
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\"}}}"
|
|
583
606
|
```
|
|
584
607
|
|
|
585
|
-
**Windows
|
|
608
|
+
**Windows (PowerShell)**
|
|
586
609
|
|
|
587
610
|
```powershell
|
|
588
611
|
$CLOUD_RUN_URL = "https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app"
|
|
@@ -595,17 +618,17 @@ curl.exe -i "$CLOUD_RUN_URL/mcp" -X POST -H "Content-Type: application/json" `
|
|
|
595
618
|
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"0.0.1"}}}'
|
|
596
619
|
```
|
|
597
620
|
|
|
598
|
-
###
|
|
621
|
+
### Step 8 — Add your user ID to ALLOWED_SUBJECTS
|
|
599
622
|
|
|
600
|
-
Dynamic Client Registration
|
|
601
|
-
|
|
602
|
-
|
|
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_`).
|
|
603
626
|
|
|
604
|
-
`ALLOWED_SUBJECTS`
|
|
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.
|
|
605
628
|
|
|
606
|
-
1.
|
|
607
|
-
`sub`
|
|
608
|
-
2.
|
|
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).
|
|
609
632
|
|
|
610
633
|
```bash
|
|
611
634
|
gcloud run services logs read pubmed-mcp \
|
|
@@ -615,7 +638,7 @@ Dynamic Client Registration では誰でも OAuth クライアントを登録で
|
|
|
615
638
|
# → ... [auth] forbidden subject: user_01XXXXXXXXXXXXXXXXXXXXXXXX
|
|
616
639
|
```
|
|
617
640
|
|
|
618
|
-
3.
|
|
641
|
+
3. Set that value. Because this uses `--update-env-vars`, the other environment variables stay as they are.
|
|
619
642
|
|
|
620
643
|
```bash
|
|
621
644
|
gcloud run services update pubmed-mcp \
|
|
@@ -623,44 +646,44 @@ Dynamic Client Registration では誰でも OAuth クライアントを登録で
|
|
|
623
646
|
--update-env-vars ALLOWED_SUBJECTS=user_01XXXXXXXXXXXXXXXXXXXXXXXX
|
|
624
647
|
```
|
|
625
648
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
4.
|
|
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.
|
|
629
652
|
|
|
630
653
|
---
|
|
631
654
|
|
|
632
|
-
###
|
|
655
|
+
### Client connection settings
|
|
633
656
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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`).
|
|
638
661
|
|
|
639
|
-
**Claude
|
|
662
|
+
**Claude (Web and Claude Desktop) — custom connector**
|
|
640
663
|
|
|
641
|
-
|
|
642
|
-
|
|
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.
|
|
643
666
|
|
|
644
|
-
- Pro / Max
|
|
645
|
-
1. [claude.ai](https://claude.ai)
|
|
646
|
-
2. **+** → **Add custom connector**
|
|
647
|
-
3. URL
|
|
648
|
-
4.
|
|
649
|
-
- Team / Enterprise
|
|
650
|
-
1.
|
|
651
|
-
2. URL
|
|
652
|
-
3.
|
|
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**
|
|
653
676
|
|
|
654
|
-
|
|
655
|
-
|
|
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.
|
|
656
679
|
|
|
657
|
-
Claude Desktop
|
|
658
|
-
Desktop
|
|
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).
|
|
659
682
|
|
|
660
|
-
**Claude
|
|
683
|
+
**Claude mobile apps (Android / iOS)**
|
|
661
684
|
|
|
662
|
-
Web
|
|
663
|
-
|
|
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.
|
|
664
687
|
|
|
665
688
|
**Claude Code**
|
|
666
689
|
|
|
@@ -668,10 +691,10 @@ Web 版または Claude Desktop で追加したカスタムコネクタは、同
|
|
|
668
691
|
claude mcp add --transport http --scope user pubmed-remote https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/mcp
|
|
669
692
|
```
|
|
670
693
|
|
|
671
|
-
Claude Code
|
|
672
|
-
|
|
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.
|
|
673
696
|
|
|
674
|
-
**Anthropic API
|
|
697
|
+
**Anthropic API (Python)**
|
|
675
698
|
|
|
676
699
|
```python
|
|
677
700
|
import os
|
|
@@ -694,20 +717,20 @@ response = client.beta.messages.create(
|
|
|
694
717
|
)
|
|
695
718
|
```
|
|
696
719
|
|
|
697
|
-
MCP
|
|
698
|
-
|
|
699
|
-
|
|
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.
|
|
700
723
|
|
|
701
|
-
|
|
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)):
|
|
702
725
|
|
|
703
|
-
1. `npx @modelcontextprotocol/inspector`
|
|
704
|
-
2. **
|
|
705
|
-
3. **Open Auth Settings** → **Quick OAuth Flow**
|
|
706
|
-
4. **Authentication complete
|
|
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
|
|
707
730
|
|
|
708
731
|
---
|
|
709
732
|
|
|
710
|
-
###
|
|
733
|
+
### Updating the image (redeploying)
|
|
711
734
|
|
|
712
735
|
```bash
|
|
713
736
|
cd PubMed_MCP
|
|
@@ -722,83 +745,83 @@ gcloud run services update pubmed-mcp \
|
|
|
722
745
|
--image asia-northeast1-docker.pkg.dev/MY_PROJECT_ID/pubmed-mcp/server:latest
|
|
723
746
|
```
|
|
724
747
|
|
|
725
|
-
`--image`
|
|
748
|
+
An update that specifies only `--image` keeps the existing environment variables and secrets.
|
|
726
749
|
|
|
727
750
|
---
|
|
728
751
|
|
|
729
|
-
##
|
|
752
|
+
## Development commands
|
|
730
753
|
|
|
731
754
|
```bash
|
|
732
|
-
npm run build # TypeScript
|
|
733
|
-
npm run dev # tsx
|
|
734
|
-
npm run cli -- search "CRISPR" --max-results 5 # CLI
|
|
735
|
-
npm test #
|
|
736
|
-
npm run typecheck #
|
|
737
|
-
npm run lint # ESLint
|
|
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
|
|
738
761
|
```
|
|
739
762
|
|
|
740
|
-
### npm
|
|
763
|
+
### Releasing to npm (for maintainers)
|
|
741
764
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
npm
|
|
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.
|
|
745
768
|
|
|
746
|
-
|
|
769
|
+
PRs that make changes add them to `[Unreleased]` in [`CHANGELOG.md`](CHANGELOG.md). To release:
|
|
747
770
|
|
|
748
|
-
1.
|
|
749
|
-
|
|
750
|
-
2.
|
|
751
|
-
3. GitHub
|
|
752
|
-
4.
|
|
753
|
-
5.
|
|
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>`
|
|
754
777
|
|
|
755
|
-
|
|
756
|
-
|
|
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`.
|
|
757
780
|
|
|
758
|
-
|
|
781
|
+
The first release is published by hand, because Trusted Publishing can't be configured until the package exists on npm.
|
|
759
782
|
|
|
760
|
-
1.
|
|
761
|
-
2. npmjs.com
|
|
762
|
-
**Allowed actions
|
|
763
|
-
3.
|
|
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**
|
|
764
787
|
|
|
765
788
|
---
|
|
766
789
|
|
|
767
|
-
##
|
|
790
|
+
## Environment variables
|
|
768
791
|
|
|
769
|
-
|
|
|
770
|
-
|
|
771
|
-
| `NCBI_API_KEY` |
|
|
772
|
-
| `PORT` |
|
|
773
|
-
| `OAUTH_ISSUER` | HTTP
|
|
774
|
-
| `OAUTH_JWKS_URL` | HTTP
|
|
775
|
-
| `MCP_RESOURCE_URL` | HTTP
|
|
776
|
-
| `ALLOWED_SUBJECTS` |
|
|
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 |
|
|
777
800
|
|
|
778
|
-
`OAUTH_ISSUER
|
|
779
|
-
|
|
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.
|
|
780
803
|
|
|
781
|
-
`.env`
|
|
782
|
-
|
|
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`.
|
|
783
806
|
|
|
784
807
|
---
|
|
785
808
|
|
|
786
|
-
##
|
|
809
|
+
## References
|
|
787
810
|
|
|
788
|
-
- [MCP
|
|
789
|
-
- [NCBI E-utilities
|
|
790
|
-
- [NCBI API
|
|
791
|
-
- [PubMed
|
|
792
|
-
- [Cloud Run
|
|
793
|
-
- [Secret Manager
|
|
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)
|
|
794
817
|
|
|
795
818
|
---
|
|
796
819
|
|
|
797
|
-
##
|
|
820
|
+
## License
|
|
798
821
|
|
|
799
|
-
|
|
822
|
+
The source code in this repository is released under the [MIT License](LICENSE).
|
|
800
823
|
|
|
801
|
-
-
|
|
802
|
-
- PubMed / PMC
|
|
803
|
-
-
|
|
804
|
-
- MIT License
|
|
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`.
|