@cyanheads/openalex-mcp-server 0.7.13 → 0.7.15
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/AGENTS.md +25 -10
- package/CLAUDE.md +25 -10
- package/README.md +167 -88
- package/changelog/0.7.x/0.7.14.md +28 -0
- package/changelog/0.7.x/0.7.15.md +33 -0
- package/changelog/template.md +7 -7
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/prompts/definitions/literature-review.prompt.js +1 -1
- package/dist/mcp-server/prompts/definitions/literature-review.prompt.js.map +1 -1
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts +19 -0
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.js +32 -3
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts +12 -1
- package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/citation-graph.tool.js +55 -33
- package/dist/mcp-server/tools/definitions/citation-graph.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts +11 -0
- package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/resolve-name.tool.js +13 -1
- package/dist/mcp-server/tools/definitions/resolve-name.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts +40 -0
- package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-entities.tool.js +132 -20
- package/dist/mcp-server/tools/definitions/search-entities.tool.js.map +1 -1
- package/dist/services/openalex/openalex-service.d.ts +16 -0
- package/dist/services/openalex/openalex-service.d.ts.map +1 -1
- package/dist/services/openalex/openalex-service.js +105 -29
- package/dist/services/openalex/openalex-service.js.map +1 -1
- package/dist/services/openalex/types.d.ts +11 -0
- package/dist/services/openalex/types.d.ts.map +1 -1
- package/dist/services/openalex/types.js +7 -0
- package/dist/services/openalex/types.js.map +1 -1
- package/package.json +7 -7
- package/server.json +3 -3
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/openalex-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/openalex-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -27,105 +27,112 @@
|
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
-
##
|
|
30
|
+
## Overview
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Scholarly catalog data from [OpenAlex](https://openalex.org) — 270M+ works, 90M+ authors, 100K+ sources, plus institutions, topics, keywords, publishers, and funders. Search, filter, and aggregate across all eight entity types, resolve ambiguous names to canonical IDs, and walk the citation graph one hop at a time. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|:----------|:------------|
|
|
36
|
-
| `openalex_search_entities` | Search, filter, sort, or retrieve by ID across all 8 entity types. |
|
|
37
|
-
| `openalex_analyze_trends` | Group-by aggregation for trend and distribution analysis. |
|
|
38
|
-
| `openalex_resolve_name` | Resolve a name or an identifier (DOI, ORCID, ROR, PMID, ISSN, OpenAlex ID) to an OpenAlex ID. |
|
|
39
|
-
| `openalex_get_citation_graph` | Walk the citation graph one hop from a seed work: cites, cited_by, or related_to. |
|
|
40
|
-
| `openalex_describe_fields` | List valid filter, group_by, and select field names for an entity type — call before building a query to avoid invalid-field errors. |
|
|
34
|
+
### Tools
|
|
41
35
|
|
|
42
|
-
|
|
36
|
+
| Tool | Description |
|
|
37
|
+
|:---|:---|
|
|
38
|
+
| `openalex_search_entities` | Search, filter, sort, or retrieve by ID across all 8 entity types |
|
|
39
|
+
| `openalex_analyze_trends` | Group-by aggregation for trend and distribution analysis |
|
|
40
|
+
| `openalex_resolve_name` | Resolve a name or an identifier (DOI, ORCID, ROR, PMID, ISSN, OpenAlex ID) to an OpenAlex ID |
|
|
41
|
+
| `openalex_get_citation_graph` | Walk the citation graph one hop from a seed work: `cites`, `cited_by`, or `related_to` |
|
|
42
|
+
| `openalex_describe_fields` | List valid filter, group_by, and select field names for an entity type |
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
### Prompts
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
- Sensible default field selection per entity type, applied to both searches and ID lookups — prevents oversized responses; pass `select` to choose fields, or `["*"]` for the full record
|
|
51
|
-
- Invalid `select` field names produce an error listing the valid fields for that entity type
|
|
52
|
-
- Formatted MCP output is a generic markdown renderer — every returned field is surfaced without per-entity-type hard-coding
|
|
53
|
-
- Cursor pagination and up to 100 results per page; `sort` takes a single key or a comma-separated list, with the `-` descending prefix applied per key
|
|
54
|
-
- `display_name` is nullable — OpenAlex holds no title for paratext and other untitled records, which pass through instead of failing the whole page
|
|
55
|
-
|
|
56
|
-
---
|
|
46
|
+
| Prompt | Description |
|
|
47
|
+
|:-------|:------------|
|
|
48
|
+
| `openalex_literature_review` | Guides a systematic literature search: formulate query, search, filter, analyze citation network, synthesize findings |
|
|
49
|
+
| `openalex_research_landscape` | Analyzes the research landscape for a topic: volume trends, top authors/institutions, open access rates, funding sources |
|
|
57
50
|
|
|
58
|
-
|
|
51
|
+
## Capability reference
|
|
59
52
|
|
|
60
|
-
|
|
53
|
+
### `openalex_search_entities` <sub>tool</sub>
|
|
61
54
|
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
55
|
+
- Retrieve a single entity by ID — OpenAlex ID, DOI, ORCID, ROR, PMID, ISSN, or PMCID (bare or URL form), or a keyword by its slug or keyword URL. `id` takes precedence: search parameters passed alongside it are dropped, and the response names which ones. A PMCID resolves nothing (OpenAlex indexes none) — use the work's PMID or DOI instead
|
|
56
|
+
- Keyword search (boolean operators, quoted phrases, wildcards, fuzzy match) plus `exact` and `semantic` search modes — semantic ranks at most 50 candidates at ~1 req/sec, and its `meta.count` reports that ceiling rather than a match total
|
|
57
|
+
- Rich filter syntax: AND across fields, OR within a field (`|`), NOT (`!`), ranges, comparisons; a comma inside a filter value is rejected (use `|`, or a `.search` filter for free text)
|
|
58
|
+
- `select` returns a curated per-entity-type default unless overridden, or `["*"]` for the full record; invalid field names error with the valid set
|
|
59
|
+
- Cursor pagination for keyword and exact search, up to 100 results per page (default 25); semantic search walks its candidates with `page` (1-based) instead, and mixing either knob with the wrong `search_mode` is rejected before the upstream call
|
|
60
|
+
- `sample` (up to 100, single page only — neither `cursor` nor `page` applies) plus a deterministic `seed` for reproducible random sampling
|
|
61
|
+
- `display_name` is nullable for untitled records; every call reports OpenAlex daily-budget cost and remaining balance
|
|
66
62
|
|
|
67
63
|
---
|
|
68
64
|
|
|
69
|
-
### `
|
|
65
|
+
### `openalex_analyze_trends` <sub>tool</sub>
|
|
70
66
|
|
|
71
|
-
|
|
67
|
+
- Group any supported field for trend, distribution, or comparative analysis; combine with `filters` to scope the population before aggregation
|
|
68
|
+
- Up to 200 groups per page (default). `order: "count"` (default) returns the top-N by count with no further pages; `order: "key"` enumerates all distinct values key-ascending with cursor pagination
|
|
69
|
+
- `include_unknown` (default `false`) adds a group for entities with no value for the grouped field
|
|
70
|
+
- Not every field is groupable — raw date fields, `.search` operators, and `from_*`/`to_*` range modifiers are rejected; check with `openalex_describe_fields(entity_type, "group_by")`
|
|
71
|
+
- Reports OpenAlex daily-budget cost and remaining balance — aggregation is priced far below paging the same entities
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
-
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### `openalex_resolve_name` <sub>tool</sub>
|
|
76
|
+
|
|
77
|
+
- A name or partial name runs an autocomplete search: up to 10 matches with disambiguation hints (last institution, host organization, place, etc.)
|
|
78
|
+
- An identifier — OpenAlex ID, DOI, ORCID, ROR, PMID, ISSN, or keyword URL, bare or in URL form — resolves directly to the one record it addresses; no `entity_type` needed, since the identifier determines its own. A PMCID is recognized but resolves nothing — OpenAlex indexes none
|
|
79
|
+
- `filters` narrows autocomplete only; on an identifier lookup they're ignored and named in a notice
|
|
80
|
+
- Reports OpenAlex daily-budget cost and remaining balance
|
|
78
81
|
|
|
79
82
|
---
|
|
80
83
|
|
|
81
|
-
### `openalex_get_citation_graph`
|
|
84
|
+
### `openalex_get_citation_graph` <sub>tool</sub>
|
|
82
85
|
|
|
83
|
-
|
|
86
|
+
- `direction` sets the edge: `cites` (works citing the seed), `cited_by` (the seed's own reference list), `related_to` (OpenAlex's algorithmic related works, ~8-30 typical, may be empty)
|
|
87
|
+
- `seed_id` accepts an OpenAlex ID, DOI, or PMID (PMCID recognized but resolves nothing); validated against a live lookup first, so a non-existent seed fails as `NotFound` rather than returning an empty graph
|
|
88
|
+
- Stacks with `filters`/`sort`/`select` to narrow the graph; `filters` cannot set `cites`/`cited_by`/`related_to`, nor an alias of one such as `cited_works` — those keys are reserved for `direction`
|
|
89
|
+
- Cursor pagination, up to 100 results per page (default 25)
|
|
90
|
+
- Reports OpenAlex daily-budget cost, covering both the seed-validation lookup and the graph page, plus remaining balance
|
|
84
91
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
-
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### `openalex_describe_fields` <sub>tool</sub>
|
|
95
|
+
|
|
96
|
+
- Lists every valid field name for an entity type + context (`filter`, `group_by`, `select`) — the complete pool, never truncated
|
|
97
|
+
- `group_by` is the filter set minus raw date fields, `.search`/`.search.exact` operators, and `from_*`/`to_*` range modifiers, which OpenAlex rejects as aggregation keys
|
|
98
|
+
- Optional `query` reorders results by name similarity without dropping any field — a nested value's parent object stays reachable further down the list
|
|
99
|
+
- Backed by a generated field catalog — no live API calls
|
|
90
100
|
|
|
91
101
|
---
|
|
92
102
|
|
|
93
|
-
### `
|
|
103
|
+
### `openalex_literature_review` <sub>prompt</sub>
|
|
94
104
|
|
|
95
|
-
|
|
105
|
+
- Arguments: `topic` required; `scope` (`narrow` / `broad`) optional, defaults to `narrow`
|
|
106
|
+
- Returns one user message walking a 6-step workflow: resolve entities, search literature, identify key papers, trace citations, analyze the landscape, synthesize findings
|
|
107
|
+
- `scope` changes the search step: `narrow` favors exact search with tight topic filters; `broad` adds semantic search across multiple related topic IDs
|
|
96
108
|
|
|
97
|
-
|
|
98
|
-
- `group_by` returns the subset of the `filter` set OpenAlex can aggregate — raw date fields, `*.search` operators, and `from_*`/`to_*` range modifiers are excluded
|
|
99
|
-
- Pass `query` (a partial or guessed name) to rank results by name similarity — surfaces the right field when you only know roughly what you want. Ranking reorders the list without shortening it, so a nested value's parent object is still reachable further down
|
|
100
|
-
- Complements the ranked "did you mean" suggestions now appended to invalid-field errors on the search, trends, and citation-graph tools
|
|
109
|
+
---
|
|
101
110
|
|
|
102
|
-
|
|
111
|
+
### `openalex_research_landscape` <sub>prompt</sub>
|
|
103
112
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
| `openalex_research_landscape` | Analyzes the research landscape for a topic: volume trends, top authors/institutions, open access rates, funding sources. |
|
|
113
|
+
- Arguments: `topic` required
|
|
114
|
+
- Returns one user message walking a 7-step quantitative workflow: resolve the topic ID, volume trends, top contributors (institutions/countries/journals), open access rate, funding sources, most-cited works, emerging fronts
|
|
115
|
+
- The funding step groups by `awards.funder_id` (resolve names via `openalex_resolve_name`) or `awards.funder_display_name` for readable labels in a single hop
|
|
108
116
|
|
|
109
117
|
## Features
|
|
110
118
|
|
|
111
|
-
Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core):
|
|
112
|
-
|
|
113
|
-
- Declarative tool definitions — single file per tool, framework handles registration and validation
|
|
114
|
-
- Unified error handling across all tools
|
|
115
|
-
- Pluggable auth (`none`, `jwt`, `oauth`)
|
|
116
|
-
- Swappable storage backends via the framework (not currently used by this server)
|
|
117
|
-
- Structured logging with optional OpenTelemetry tracing
|
|
118
|
-
- Runs locally (stdio/HTTP) or in Docker from the same codebase
|
|
119
|
+
Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): stdio and Streamable HTTP transports, pluggable auth (`none` / `jwt` / `oauth`), swappable storage (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`), structured logging with optional OpenTelemetry tracing.
|
|
119
120
|
|
|
120
121
|
OpenAlex-specific:
|
|
121
122
|
|
|
122
|
-
- Typed API client with automatic ID normalization (DOI, ORCID, ROR, PMID, PMCID, ISSN, OpenAlex and PubMed/PubMed Central URLs)
|
|
123
|
-
-
|
|
124
|
-
- HTTP status codes mapped to specific MCP error classes (400 → InvalidParams, 422 → ValidationError, 429 → RateLimited
|
|
125
|
-
- Every API-calling tool reports what the call spent against the OpenAlex daily budget and what is left of it, so a paginated sweep can be priced before it runs instead of ending in a 429. An account holding prepaid balance sees that too, since it keeps serving once the day's allowance is gone
|
|
123
|
+
- Typed API client with automatic ID normalization (DOI, ORCID, ROR, PMID, PMCID, ISSN, OpenAlex and PubMed/PubMed Central URLs); a PMCID normalizes but resolves nothing since OpenAlex indexes none
|
|
124
|
+
- Keyless by default — an optional API key raises rate and daily-budget limits, and an optional `mailto` identifies the caller to OpenAlex's polite pool
|
|
125
|
+
- HTTP status codes mapped to specific MCP error classes (400 → InvalidParams, 422 → ValidationError, 429 → RateLimited) with upstream messages surfaced
|
|
126
126
|
- Timeout-aware request retries and cancellation support via `AbortSignal`
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
Agent-friendly output:
|
|
129
|
+
|
|
130
|
+
- Provenance — every API-calling tool reports OpenAlex daily-budget cost, remaining balance, and reset time (`budget.costUsd`, `remainingUsd`, `resetsInSeconds`)
|
|
131
|
+
- Effective-query echo — search, trends, and citation-graph responses echo the criteria that actually ran, so an empty result is diagnosable without re-reading the request
|
|
132
|
+
- Discriminated output contracts — typed error reasons (`entity_not_found`, `upstream_budget_exhausted`, `semantic_per_page_cap`, `reserved_filter_key`, and more) each carrying an explicit recovery hint
|
|
133
|
+
- Response shaping — abstracts are reconstructed from OpenAlex's inverted-index encoding into plaintext, and `display_name` stays `null` for untitled or paratext records instead of being backfilled
|
|
134
|
+
|
|
135
|
+
## Getting started
|
|
129
136
|
|
|
130
137
|
### Public Hosted Instance
|
|
131
138
|
|
|
@@ -144,7 +151,26 @@ A public instance is available at `https://openalex.caseyjhand.com/mcp` — no i
|
|
|
144
151
|
|
|
145
152
|
### Self-Hosted / Local
|
|
146
153
|
|
|
147
|
-
Add to your MCP client
|
|
154
|
+
Add the following to your MCP client configuration file.
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"mcpServers": {
|
|
159
|
+
"openalex-mcp-server": {
|
|
160
|
+
"type": "stdio",
|
|
161
|
+
"command": "bunx",
|
|
162
|
+
"args": ["@cyanheads/openalex-mcp-server@latest"],
|
|
163
|
+
"env": {
|
|
164
|
+
"MCP_TRANSPORT_TYPE": "stdio",
|
|
165
|
+
"MCP_LOG_LEVEL": "info",
|
|
166
|
+
"OPENALEX_API_KEY": "your-api-key"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Or with npx (no Bun required):
|
|
148
174
|
|
|
149
175
|
```json
|
|
150
176
|
{
|
|
@@ -152,94 +178,147 @@ Add to your MCP client config (e.g., `claude_desktop_config.json`):
|
|
|
152
178
|
"openalex-mcp-server": {
|
|
153
179
|
"type": "stdio",
|
|
154
180
|
"command": "npx",
|
|
155
|
-
"args": ["-y", "@cyanheads/openalex-mcp-server"],
|
|
181
|
+
"args": ["-y", "@cyanheads/openalex-mcp-server@latest"],
|
|
156
182
|
"env": {
|
|
157
|
-
"
|
|
183
|
+
"MCP_TRANSPORT_TYPE": "stdio",
|
|
184
|
+
"MCP_LOG_LEVEL": "info",
|
|
185
|
+
"OPENALEX_API_KEY": "your-api-key"
|
|
158
186
|
}
|
|
159
187
|
}
|
|
160
188
|
}
|
|
161
189
|
}
|
|
162
190
|
```
|
|
163
191
|
|
|
192
|
+
Or with Docker:
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"mcpServers": {
|
|
197
|
+
"openalex-mcp-server": {
|
|
198
|
+
"type": "stdio",
|
|
199
|
+
"command": "docker",
|
|
200
|
+
"args": [
|
|
201
|
+
"run", "-i", "--rm",
|
|
202
|
+
"-e", "MCP_TRANSPORT_TYPE=stdio",
|
|
203
|
+
"-e", "OPENALEX_API_KEY=your-api-key",
|
|
204
|
+
"ghcr.io/cyanheads/openalex-mcp-server:latest"
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
For Streamable HTTP, set the transport and start the server:
|
|
212
|
+
|
|
213
|
+
```sh
|
|
214
|
+
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 OPENALEX_API_KEY=... bun run start:http
|
|
215
|
+
# Server listens at http://localhost:3010/mcp
|
|
216
|
+
```
|
|
217
|
+
|
|
164
218
|
`OPENALEX_API_KEY` is optional — set it to a free [OpenAlex account key](https://openalex.org/settings/api) for keyed rate limits and budget under OpenAlex's usage-based pricing, or omit it for anonymous access. Set `OPENALEX_MAILTO` to an email if you want to identify yourself to OpenAlex (the [polite pool](https://developers.openalex.org/guides/authentication)).
|
|
165
219
|
|
|
166
220
|
### Prerequisites
|
|
167
221
|
|
|
168
|
-
- [Bun v1.4.0](https://bun.sh/) or higher (
|
|
222
|
+
- [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
|
|
223
|
+
- Optional: an [OpenAlex account API key](https://openalex.org/settings/api) for keyed rate limits and budget — omit for anonymous access.
|
|
169
224
|
|
|
170
225
|
### Installation
|
|
171
226
|
|
|
172
227
|
1. **Clone the repository:**
|
|
228
|
+
|
|
173
229
|
```sh
|
|
174
230
|
git clone https://github.com/cyanheads/openalex-mcp-server.git
|
|
175
231
|
```
|
|
176
232
|
|
|
177
233
|
2. **Navigate into the directory:**
|
|
234
|
+
|
|
178
235
|
```sh
|
|
179
236
|
cd openalex-mcp-server
|
|
180
237
|
```
|
|
181
238
|
|
|
182
239
|
3. **Install dependencies:**
|
|
240
|
+
|
|
183
241
|
```sh
|
|
184
242
|
bun install
|
|
185
243
|
```
|
|
186
244
|
|
|
245
|
+
4. **Configure environment:**
|
|
246
|
+
|
|
247
|
+
```sh
|
|
248
|
+
cp .env.example .env
|
|
249
|
+
# edit .env and set required vars
|
|
250
|
+
```
|
|
251
|
+
|
|
187
252
|
## Configuration
|
|
188
253
|
|
|
189
254
|
| Variable | Description | Default |
|
|
190
255
|
|:---------|:------------|:--------|
|
|
191
|
-
| `OPENALEX_API_KEY` | **Optional.** OpenAlex account API key, sent upstream as `api_key=` (free from [openalex.org/settings/api](https://openalex.org/settings/api)). Without it, anonymous rate limits apply. | — |
|
|
192
|
-
| `OPENALEX_MAILTO` | **Optional.** Email sent upstream as `mailto=` to identify yourself to OpenAlex (the [polite pool](https://developers.openalex.org/guides/authentication)). A courtesy identifier, separate from the API key. | — |
|
|
193
|
-
| `OPENALEX_BASE_URL` | OpenAlex API base URL. | `https://api.openalex.org` |
|
|
194
256
|
| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
|
|
195
257
|
| `MCP_HTTP_PORT` | Port for HTTP server. | `3010` |
|
|
258
|
+
| `MCP_SESSION_MODE` | HTTP session mode: `stateless`, `stateful`, or `auto` (resolves to `stateful`). The server declares `stateless` in code; an explicit value overrides it. | `stateless` |
|
|
196
259
|
| `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth`. | `none` |
|
|
197
|
-
| `MCP_SESSION_MODE` | HTTP session handling: `auto`, `stateful`, or `stateless`. This server ships `stateless` — set by `Dockerfile` and by `.env.example`. | `auto` (resolves to `stateful`) |
|
|
198
260
|
| `MCP_ALLOWED_ORIGINS` | Comma-separated allow-list of browser `Origin` headers for HTTP transport. Unset = loopback-only; set to `*` to disable. | _loopback only_ |
|
|
199
261
|
| `MCP_LOG_LEVEL` | Log level (RFC 5424). | `debug` |
|
|
200
262
|
| `LOGS_DIR` | Directory for log files (Node.js only). | `<project-root>/logs` |
|
|
263
|
+
| `STORAGE_PROVIDER_TYPE` | Storage backend. | `in-memory` |
|
|
264
|
+
| `OPENALEX_API_KEY` | OpenAlex account API key, sent upstream as `api_key=` (free from [openalex.org/settings/api](https://openalex.org/settings/api)). Without it, anonymous rate limits apply. | — |
|
|
265
|
+
| `OPENALEX_MAILTO` | Email sent upstream as `mailto=` to identify yourself to OpenAlex (the "polite pool"); a courtesy identifier, separate from the API key. | — |
|
|
266
|
+
| `OPENALEX_BASE_URL` | OpenAlex API base URL. | `https://api.openalex.org` |
|
|
201
267
|
| `OTEL_ENABLED` | Enable [OpenTelemetry instrumentation](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry) (spans, metrics, completion logs). | `false` |
|
|
202
268
|
|
|
203
|
-
|
|
269
|
+
See [`.env.example`](./.env.example) for the full list of optional overrides.
|
|
270
|
+
|
|
271
|
+
## Running the server
|
|
272
|
+
|
|
273
|
+
### Local development
|
|
204
274
|
|
|
205
|
-
|
|
275
|
+
- **Build and run:**
|
|
206
276
|
|
|
207
|
-
- **Build and run the production version:**
|
|
208
277
|
```sh
|
|
209
|
-
|
|
210
|
-
bun run
|
|
278
|
+
# One-time build
|
|
279
|
+
bun run rebuild
|
|
280
|
+
|
|
281
|
+
# Run the built server
|
|
282
|
+
bun run start:stdio
|
|
283
|
+
# or
|
|
284
|
+
bun run start:http
|
|
211
285
|
```
|
|
212
286
|
|
|
213
287
|
- **Run checks and tests:**
|
|
288
|
+
|
|
214
289
|
```sh
|
|
215
|
-
bun run devcheck
|
|
216
|
-
bun run test
|
|
290
|
+
bun run devcheck # Lints, formats, type-checks
|
|
291
|
+
bun run test # Runs the test suite
|
|
217
292
|
```
|
|
218
293
|
|
|
219
294
|
### Docker
|
|
220
295
|
|
|
221
296
|
```sh
|
|
222
297
|
docker build -t openalex-mcp-server .
|
|
223
|
-
docker run -e OPENALEX_API_KEY=your-key -p 3010:3010 openalex-mcp-server
|
|
298
|
+
docker run --rm -e OPENALEX_API_KEY=your-key -p 3010:3010 openalex-mcp-server
|
|
224
299
|
```
|
|
225
300
|
|
|
226
|
-
|
|
301
|
+
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `/var/log/openalex-mcp-server`. OpenTelemetry peer dependencies are installed by default — build with `--build-arg OTEL_ENABLED=false` to omit them.
|
|
302
|
+
|
|
303
|
+
## Project structure
|
|
227
304
|
|
|
228
305
|
| Directory | Purpose |
|
|
229
306
|
|:----------|:--------|
|
|
307
|
+
| `src/index.ts` | `createApp()` entry point — registers tools and prompts. |
|
|
308
|
+
| `src/config/` | Server-specific environment variable parsing and validation with Zod. |
|
|
230
309
|
| `src/mcp-server/tools/definitions/` | Tool definitions (`*.tool.ts`). |
|
|
231
310
|
| `src/mcp-server/prompts/definitions/` | Prompt definitions (`*.prompt.ts`). |
|
|
232
|
-
| `src/services/openalex/` | OpenAlex API client
|
|
233
|
-
| `src/config/` | Environment variable parsing and validation with Zod. |
|
|
311
|
+
| `src/services/openalex/` | OpenAlex API client, field catalog, and domain types. |
|
|
234
312
|
| `tests/` | Unit and integration tests, mirroring the `src/` structure. |
|
|
235
313
|
|
|
236
|
-
## Development
|
|
314
|
+
## Development guide
|
|
237
315
|
|
|
238
316
|
See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rules. The short version:
|
|
239
317
|
|
|
240
318
|
- Handlers throw, framework catches — no `try/catch` in tool logic
|
|
241
319
|
- Use `ctx.log` for logging, `ctx.state` for storage
|
|
242
|
-
-
|
|
320
|
+
- Wrap OpenAlex responses: validate the raw payload → normalize to a domain type → return the output schema; never fabricate missing fields
|
|
321
|
+
- Always resolve names to IDs via `openalex_resolve_name` before filtering by entity
|
|
243
322
|
|
|
244
323
|
## Contributing
|
|
245
324
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Input aliases for filter/search/name parameters, mcp-ts-core 0.13.x error-text and validation upgrades, and a code-declared stateless session default."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.7.14 — 2026-09-19
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **Input aliases** — `filter` → `filters` on `openalex_search_entities`, `openalex_resolve_name`, `openalex_analyze_trends`, and `openalex_get_citation_graph`; `search` → `query` on `openalex_search_entities`; `name` → `query` on `openalex_resolve_name`. Rewritten only when the declared key is absent and not advertised in `inputSchema`.
|
|
12
|
+
|
|
13
|
+
## Changed
|
|
14
|
+
|
|
15
|
+
- **Session mode** — the server now declares `stateless` as its HTTP session-mode default in code; an explicitly set `MCP_SESSION_MODE` still overrides it. The Docker image and `.env.example` already pinned `stateless`.
|
|
16
|
+
- **Tool error `content[]` text** now ends with `(reason <reason> · not retryable)` (or `· retryable`) whenever the error carries a `data.reason` or a boolean `data.retryable`. The `Recovery:` line is dropped when the message already contains the hint word for word. `structuredContent` is unchanged.
|
|
17
|
+
- **Argument-validation rejections** carry `data.reason: "invalid_arguments"` and a `Recovery:` hint derived from the schema, and an omitted required field reads as missing rather than as a wrong choice.
|
|
18
|
+
- **Tool arguments now pass a pre-validation step** before schema validation: undeclared root keys a client adds are dropped, key spellings differing only by case/`-`/`_` are rewritten, and a JSON-stringified array is repaired once after a failed parse.
|
|
19
|
+
- Framework skills, scripts, issue templates, and CI workflow synced with mcp-ts-core 0.13.1–0.13.5 (adds `.github/workflows/codeql.yml`, a README version-badge packaging check, and `config/`-aware build tsconfig probing); `withRetry`'s per-attempt signal now reaches `fetch`, and two handlers' precondition checks moved into module-level helpers.
|
|
20
|
+
|
|
21
|
+
## Dependencies
|
|
22
|
+
|
|
23
|
+
- `@cyanheads/mcp-ts-core` ^0.13.0 → ^0.13.5
|
|
24
|
+
- `zod` ^4.6.1 → ^4.6.5
|
|
25
|
+
- `@biomejs/biome` ^2.5.13 → ^2.5.14
|
|
26
|
+
- `@types/node` ^26.5.1 → ^26.6.1
|
|
27
|
+
- `tsc-alias` ^1.9.4 → ^1.9.5
|
|
28
|
+
- `vitest` ^5.0.0 → ^5.0.1
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Keyword identifiers, semantic-mode `page` pagination, AND'd filter aliases, and honest pagination/notice handling across the paginating tools."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.7.15 — 2026-09-22
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **Keyword identifiers** resolve via their OpenAlex URL or bare slug (`https://openalex.org/keywords/groundwater` or `groundwater`) — keywords never had a native alphanumeric ID, so the removed `K`-prefixed routing arm 404'd. ([#68](https://github.com/cyanheads/openalex-mcp-server/issues/68))
|
|
12
|
+
- **`page`** input on `openalex_search_entities` paginates semantic search (1-based), the one mode OpenAlex rejects `cursor` on. Combining `page`/`cursor`/`sample` with the wrong mode now throws `semantic_with_cursor`, `page_without_semantic`, or `sample_with_page`. ([#71](https://github.com/cyanheads/openalex-mcp-server/issues/71))
|
|
13
|
+
- **`best_oa_location`** joins the default `works` projection, naming a green-OA work's readable copy (`pdf_url`, `license`, `version`, hosting source) instead of leaving only the closed publisher location. ([#79](https://github.com/cyanheads/openalex-mcp-server/issues/79))
|
|
14
|
+
- **`query_too_long`** error reason on `openalex_search_entities` — an over-length `query` (HTTP 400) now maps to its own contract entry instead of the generic invalid-params one. ([#71](https://github.com/cyanheads/openalex-mcp-server/issues/71))
|
|
15
|
+
|
|
16
|
+
## Changed
|
|
17
|
+
|
|
18
|
+
- **Filter aliases** resolving to the same upstream key are now AND'd instead of silently overwriting — `{year: "2020", publication_year: "2024"}` now matches works in both years, matching OpenAlex's own repeated-key behavior. ([#70](https://github.com/cyanheads/openalex-mcp-server/issues/70))
|
|
19
|
+
- **`openalex_get_citation_graph`'s reserved-key guard** now checks the *translated* filter key, so an alias of `cites`/`cited_by`/`related_to` (e.g. `cited_works`) is caught instead of colliding silently with `direction`. ([#70](https://github.com/cyanheads/openalex-mcp-server/issues/70))
|
|
20
|
+
- **Pagination-exhausted notices** — `openalex_search_entities`, `openalex_analyze_trends`, and `openalex_get_citation_graph` now report the traversal as finished on an empty page reached via `cursor`, instead of advising a broader query. ([#74](https://github.com/cyanheads/openalex-mcp-server/issues/74))
|
|
21
|
+
- **`meta.per_page`** on `openalex_search_entities` and `openalex_get_citation_graph` now documents itself as the requested page size, not the record count returned. ([#74](https://github.com/cyanheads/openalex-mcp-server/issues/74))
|
|
22
|
+
- **`meta.count`** under `search_mode: "semantic"` is disclosed as the capped candidate ceiling (≤50), not an exhaustive match total. ([#71](https://github.com/cyanheads/openalex-mcp-server/issues/71))
|
|
23
|
+
|
|
24
|
+
## Fixed
|
|
25
|
+
|
|
26
|
+
- **Blank `id`/`query`/`cursor`/`group_by` inputs** on the paginating tools are now rejected at the schema, instead of running as an unfiltered listing or an accepted "exhausted" page. ([#69](https://github.com/cyanheads/openalex-mcp-server/issues/69), [#80](https://github.com/cyanheads/openalex-mcp-server/issues/80))
|
|
27
|
+
- **`openalex_analyze_trends`** no longer reports a successful empty aggregation when OpenAlex answers with its plain list shape (no `group_by` key) — it now throws `upstream_missing_group_by`. ([#69](https://github.com/cyanheads/openalex-mcp-server/issues/69))
|
|
28
|
+
- **`openalex_literature_review`** prompt's step 3 now re-runs the step-2 filtered search sorted by `-cited_by_count` instead of an unfiltered search, and names the fields to `select` on the detail lookup. ([#77](https://github.com/cyanheads/openalex-mcp-server/issues/77))
|
|
29
|
+
|
|
30
|
+
## Dependencies
|
|
31
|
+
|
|
32
|
+
- `@cyanheads/mcp-ts-core` ^0.13.5 → ^0.13.6
|
|
33
|
+
- `@types/node` ^26.6.1 → ^26.6.2
|
package/changelog/template.md
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
# Required. One-line GitHub Release-style headline. 350 character cap — a
|
|
8
8
|
# ceiling, not a target. Default short and scannable. Don't pad, don't stitch
|
|
9
|
-
# unrelated changes with commas/semicolons into an inventory — pick the
|
|
10
|
-
# headline
|
|
9
|
+
# unrelated changes with commas/semicolons into an inventory — pick the one
|
|
10
|
+
# headline the release is about. Quotes required: unquoted YAML treats
|
|
11
11
|
# `: ` inside the value as a key separator and fails GitHub's strict parser.
|
|
12
12
|
summary: ""
|
|
13
13
|
|
|
@@ -119,11 +119,11 @@ security: false
|
|
|
119
119
|
TAG ANNOTATIONS — the annotated tag body renders as the GitHub Release body
|
|
120
120
|
via `gh release create --notes-from-tag`. It is a condensed digest of this
|
|
121
121
|
entry, never a copy, and its format is owned by the `release-and-publish`
|
|
122
|
-
skill (step 4, "Create the annotated tag"):
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
skill (step 4, "Create the annotated tag"): a short subject line without the
|
|
123
|
+
version, flat headline bullets — no Keep-a-Changelog section headers, no
|
|
124
|
+
gates line — at most one deps line, issue backlinks, and the changelog link
|
|
125
|
+
last. In release-PR mode the `git-wrapup` skill authors those bullets as the
|
|
126
|
+
PR body's `## Changes` and the tag copies them.
|
|
127
127
|
-->
|
|
128
128
|
|
|
129
129
|
## Added
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,8 @@ import { initOpenAlexService } from './services/openalex/openalex-service.js';
|
|
|
15
15
|
await createApp({
|
|
16
16
|
name: 'openalex-mcp-server',
|
|
17
17
|
title: 'openalex-mcp-server',
|
|
18
|
+
// No tool gates on ctx.requestInput, so every call completes in a single round trip.
|
|
19
|
+
sessionMode: 'stateless',
|
|
18
20
|
tools: [
|
|
19
21
|
resolveNameTool,
|
|
20
22
|
searchEntitiesTool,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAC;AACtG,OAAO,EAAE,uBAAuB,EAAE,MAAM,+DAA+D,CAAC;AACxG,OAAO,EAAE,iBAAiB,EAAE,MAAM,uDAAuD,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,MAAM,uDAAuD,CAAC;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,wDAAwD,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAE,MAAM,qDAAqD,CAAC;AACtF,OAAO,EAAE,kBAAkB,EAAE,MAAM,wDAAwD,CAAC;AAC5F,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAE9E,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,qBAAqB;IAC3B,KAAK,EAAE,qBAAqB;IAC5B,KAAK,EAAE;QACL,eAAe;QACf,kBAAkB;QAClB,iBAAiB;QACjB,oBAAoB;QACpB,kBAAkB;KACnB;IACD,OAAO,EAAE,CAAC,sBAAsB,EAAE,uBAAuB,CAAC;IAC1D,YAAY,EACV,iUAAiU;IACnU,OAAO,EAAE;QACP,OAAO,EAAE,yEAAyE;QAClF,WAAW,EAAE,KAAK;QAClB,QAAQ,EAAE,kDAAkD;QAC5D,UAAU,EAAE;YACV,gBAAgB,EAAE,uBAAuB;SAC1C;QACD,KAAK,EAAE;YACL,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,sBAAsB,EAAE;YACnD,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,iCAAiC,EAAE;SAC/D;KACF;IACD,KAAK;QACH,mBAAmB,EAAE,CAAC;IACxB,CAAC;CACF,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAC;AACtG,OAAO,EAAE,uBAAuB,EAAE,MAAM,+DAA+D,CAAC;AACxG,OAAO,EAAE,iBAAiB,EAAE,MAAM,uDAAuD,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,MAAM,uDAAuD,CAAC;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,wDAAwD,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAE,MAAM,qDAAqD,CAAC;AACtF,OAAO,EAAE,kBAAkB,EAAE,MAAM,wDAAwD,CAAC;AAC5F,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAE9E,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,qBAAqB;IAC3B,KAAK,EAAE,qBAAqB;IAC5B,qFAAqF;IACrF,WAAW,EAAE,WAAW;IACxB,KAAK,EAAE;QACL,eAAe;QACf,kBAAkB;QAClB,iBAAiB;QACjB,oBAAoB;QACpB,kBAAkB;KACnB;IACD,OAAO,EAAE,CAAC,sBAAsB,EAAE,uBAAuB,CAAC;IAC1D,YAAY,EACV,iUAAiU;IACnU,OAAO,EAAE;QACP,OAAO,EAAE,yEAAyE;QAClF,WAAW,EAAE,KAAK;QAClB,QAAQ,EAAE,kDAAkD;QAC5D,UAAU,EAAE;YACV,gBAAgB,EAAE,uBAAuB;SAC1C;QACD,KAAK,EAAE;YACL,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,sBAAsB,EAAE;YACnD,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,iCAAiC,EAAE;SAC/D;KACF;IACD,KAAK;QACH,mBAAmB,EAAE,CAAC;IACxB,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -41,7 +41,7 @@ Follow this workflow using the OpenAlex tools:
|
|
|
41
41
|
- Search with multiple related topic IDs to capture adjacent areas`}
|
|
42
42
|
Use select to keep payloads manageable.
|
|
43
43
|
|
|
44
|
-
3. **Identify key papers** —
|
|
44
|
+
3. **Identify key papers** — Re-run the step 2 search with sort: "-cited_by_count" to surface landmark works, keeping the topical filters in place: an explicit sort replaces relevance ranking, so without them you get the most-cited works in the catalog rather than in "${args.topic}". Then call openalex_search_entities with id and a select naming the fields you need (e.g. ["abstract", "authorships", "primary_location"]) for the most important ones — an id lookup on its own returns the curated default projection.
|
|
45
45
|
|
|
46
46
|
4. **Trace citations** — For each key paper, use openalex_get_citation_graph with direction "cites" to find subsequent work that cites it, and direction "cited_by" to find foundational work it references.
|
|
47
47
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"literature-review.prompt.js","sourceRoot":"","sources":["../../../../src/mcp-server/prompts/definitions/literature-review.prompt.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAEnD,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC,4BAA4B,EAAE;IACzE,WAAW,EACT,wHAAwH;IAC1H,SAAS,EACP,2HAA2H;IAC7H,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACb,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,QAAQ,CACP,+FAA+F,CAChG;QACH,KAAK,EAAE,CAAC;aACL,IAAI,CAAC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;aACzB,QAAQ,EAAE;aACV,QAAQ,CACP,wGAAwG,CACzG;KACJ,CAAC;IACF,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE;QACjB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC;QACrC,OAAO;YACL;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE;oBACP,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,+CAA+C,IAAI,CAAC,KAAK;;SAEhE,KAAK;;;;mHAIqG,IAAI,CAAC,KAAK;;iFAGjH,KAAK,KAAK,QAAQ;wBAChB,CAAC,CAAC;;;qCAGqB;wBACvB,CAAC,CAAC;;;sEAIN
|
|
1
|
+
{"version":3,"file":"literature-review.prompt.js","sourceRoot":"","sources":["../../../../src/mcp-server/prompts/definitions/literature-review.prompt.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAEnD,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC,4BAA4B,EAAE;IACzE,WAAW,EACT,wHAAwH;IAC1H,SAAS,EACP,2HAA2H;IAC7H,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACb,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,QAAQ,CACP,+FAA+F,CAChG;QACH,KAAK,EAAE,CAAC;aACL,IAAI,CAAC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;aACzB,QAAQ,EAAE;aACV,QAAQ,CACP,wGAAwG,CACzG;KACJ,CAAC;IACF,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE;QACjB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC;QACrC,OAAO;YACL;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE;oBACP,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,+CAA+C,IAAI,CAAC,KAAK;;SAEhE,KAAK;;;;mHAIqG,IAAI,CAAC,KAAK;;iFAGjH,KAAK,KAAK,QAAQ;wBAChB,CAAC,CAAC;;;qCAGqB;wBACvB,CAAC,CAAC;;;sEAIN;;;+QAGqQ,IAAI,CAAC,KAAK;;;;;;;;;+IAS1I;iBACtI;aACF;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -41,64 +41,83 @@ export declare const analyzeTrendsTool: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
41
41
|
readonly when: "OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429).";
|
|
42
42
|
readonly retryable: true;
|
|
43
43
|
readonly recovery: "Wait several seconds and retry; consider lowering request frequency for this caller.";
|
|
44
|
+
readonly thrownBy: "service";
|
|
44
45
|
}, {
|
|
45
46
|
readonly reason: "upstream_budget_exhausted";
|
|
46
47
|
readonly code: JsonRpcErrorCode.RateLimited;
|
|
47
48
|
readonly when: "The OpenAlex daily usage budget is spent (HTTP 429).";
|
|
48
49
|
readonly retryable: false;
|
|
49
50
|
readonly recovery: "The daily budget refills at midnight UTC — retrying sooner will not succeed. Set OPENALEX_API_KEY to a free key (https://openalex.org/settings/api) for a larger daily budget than anonymous access, or wait for the reset.";
|
|
51
|
+
readonly thrownBy: "service";
|
|
50
52
|
}, {
|
|
51
53
|
readonly reason: "upstream_timeout";
|
|
52
54
|
readonly code: JsonRpcErrorCode.Timeout;
|
|
53
55
|
readonly when: "OpenAlex did not respond within the request deadline.";
|
|
54
56
|
readonly retryable: true;
|
|
55
57
|
readonly recovery: "Retry after a short delay; if timeouts persist, narrow the request with tighter filters to reduce upstream load.";
|
|
58
|
+
readonly thrownBy: "service";
|
|
56
59
|
}, {
|
|
57
60
|
readonly reason: "upstream_unavailable";
|
|
58
61
|
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
59
62
|
readonly when: "OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON.";
|
|
60
63
|
readonly retryable: true;
|
|
61
64
|
readonly recovery: "Wait and retry; check https://openalex.org for service status if the outage persists.";
|
|
65
|
+
readonly thrownBy: "service";
|
|
62
66
|
}, {
|
|
63
67
|
readonly reason: "upstream_unauthorized";
|
|
64
68
|
readonly code: JsonRpcErrorCode.Unauthorized;
|
|
65
69
|
readonly when: "OpenAlex rejected the API key (HTTP 401).";
|
|
66
70
|
readonly recovery: "Check that OPENALEX_API_KEY is set to a valid OpenAlex account API key (free from https://openalex.org/settings/api).";
|
|
71
|
+
readonly thrownBy: "service";
|
|
67
72
|
}, {
|
|
68
73
|
readonly reason: "upstream_forbidden";
|
|
69
74
|
readonly code: JsonRpcErrorCode.Forbidden;
|
|
70
75
|
readonly when: "OpenAlex denied access to the requested resource (HTTP 403).";
|
|
71
76
|
readonly recovery: "Confirm the API key has access to this entity type or endpoint, then retry the request.";
|
|
77
|
+
readonly thrownBy: "service";
|
|
72
78
|
}, {
|
|
73
79
|
readonly reason: "comma_in_filter_value";
|
|
74
80
|
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
75
81
|
readonly when: "A filter value contains a comma, which collides with the OpenAlex filter separator.";
|
|
76
82
|
readonly recovery: "Use `|` for OR within a filter value (e.g. \"2020|2021\"), or use a `.search` filter or the `query` parameter for free-text phrases that contain commas.";
|
|
83
|
+
readonly thrownBy: "service";
|
|
77
84
|
}, {
|
|
78
85
|
readonly reason: "upstream_invalid_params";
|
|
79
86
|
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
80
87
|
readonly when: "OpenAlex rejected an invalid group_by or filter field name (HTTP 400).";
|
|
81
88
|
readonly recovery: "The upstream message names the rejected key and suggests close matches. Use openalex_describe_fields(entity_type, \"group_by\") to browse valid group_by fields, or openalex_describe_fields(entity_type, \"filter\") for filter fields.";
|
|
89
|
+
readonly thrownBy: "service";
|
|
82
90
|
}, {
|
|
83
91
|
readonly reason: "upstream_invalid_id_value";
|
|
84
92
|
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
85
93
|
readonly when: "An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400).";
|
|
86
94
|
readonly recovery: "Call openalex_resolve_name to turn the name into an OpenAlex ID, then filter by that ID. Entity filters such as authorships.author.id and primary_topic.id accept IDs only.";
|
|
95
|
+
readonly thrownBy: "service";
|
|
87
96
|
}, {
|
|
88
97
|
readonly reason: "upstream_ungroupable_group_by";
|
|
89
98
|
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
90
99
|
readonly when: "group_by targets a raw date, float, or *.search field OpenAlex cannot aggregate (HTTP 400).";
|
|
91
100
|
readonly recovery: "Group by a categorical or year field (e.g. publication_year, type, oa_status, or an integer count field) — raw date fields and *.search operators cannot be grouped. Call openalex_describe_fields(entity_type, \"group_by\") for the groupable set.";
|
|
101
|
+
readonly thrownBy: "service";
|
|
92
102
|
}, {
|
|
93
103
|
readonly reason: "upstream_invalid_params_other";
|
|
94
104
|
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
95
105
|
readonly when: "OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name.";
|
|
96
106
|
readonly recovery: "Read the upstream message in the error above and adjust the request — check filter operators, value formats, and the group_by field.";
|
|
107
|
+
readonly thrownBy: "service";
|
|
97
108
|
}, {
|
|
98
109
|
readonly reason: "upstream_validation_failed";
|
|
99
110
|
readonly code: JsonRpcErrorCode.ValidationError;
|
|
100
111
|
readonly when: "OpenAlex rejected the request as semantically invalid (HTTP 422).";
|
|
101
112
|
readonly recovery: "Read the upstream message for the specific field, then adjust the request to satisfy validation.";
|
|
113
|
+
readonly thrownBy: "service";
|
|
114
|
+
}, {
|
|
115
|
+
readonly reason: "upstream_missing_group_by";
|
|
116
|
+
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
117
|
+
readonly when: "OpenAlex answered with its plain list shape, carrying no aggregation for the requested group_by.";
|
|
118
|
+
readonly retryable: true;
|
|
119
|
+
readonly recovery: "Retry the request; if it repeats, confirm group_by names a field OpenAlex can aggregate with openalex_describe_fields(entity_type, \"group_by\").";
|
|
120
|
+
readonly thrownBy: "service";
|
|
102
121
|
}], {
|
|
103
122
|
readonly echo: z.ZodString;
|
|
104
123
|
readonly totalCount: z.ZodNumber;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze-trends.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/analyze-trends.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAKjE,eAAO,MAAM,iBAAiB
|
|
1
|
+
{"version":3,"file":"analyze-trends.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/analyze-trends.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAKjE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4U5B,CAAC"}
|