@cyanheads/openalex-mcp-server 0.8.0 → 0.9.0

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.
Files changed (54) hide show
  1. package/AGENTS.md +20 -7
  2. package/CLAUDE.md +20 -7
  3. package/Dockerfile +17 -8
  4. package/README.md +20 -17
  5. package/changelog/0.9.x/0.9.0.md +48 -0
  6. package/dist/mcp-server/prompts/definitions/literature-review.prompt.js +1 -1
  7. package/dist/mcp-server/prompts/definitions/research-landscape.prompt.js +1 -1
  8. package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts +27 -2
  9. package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts.map +1 -1
  10. package/dist/mcp-server/tools/definitions/analyze-trends.tool.js +34 -4
  11. package/dist/mcp-server/tools/definitions/analyze-trends.tool.js.map +1 -1
  12. package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts +41 -4
  13. package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts.map +1 -1
  14. package/dist/mcp-server/tools/definitions/citation-graph.tool.js +62 -14
  15. package/dist/mcp-server/tools/definitions/citation-graph.tool.js.map +1 -1
  16. package/dist/mcp-server/tools/definitions/describe-fields.tool.d.ts.map +1 -1
  17. package/dist/mcp-server/tools/definitions/describe-fields.tool.js +34 -4
  18. package/dist/mcp-server/tools/definitions/describe-fields.tool.js.map +1 -1
  19. package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts +37 -1
  20. package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts.map +1 -1
  21. package/dist/mcp-server/tools/definitions/resolve-name.tool.js +175 -40
  22. package/dist/mcp-server/tools/definitions/resolve-name.tool.js.map +1 -1
  23. package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts +41 -4
  24. package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts.map +1 -1
  25. package/dist/mcp-server/tools/definitions/search-entities.tool.js +71 -23
  26. package/dist/mcp-server/tools/definitions/search-entities.tool.js.map +1 -1
  27. package/dist/mcp-server/tools/render-budget.d.ts +4 -2
  28. package/dist/mcp-server/tools/render-budget.d.ts.map +1 -1
  29. package/dist/mcp-server/tools/render-budget.js +4 -2
  30. package/dist/mcp-server/tools/render-budget.js.map +1 -1
  31. package/dist/mcp-server/tools/response-budget.d.ts +2 -1
  32. package/dist/mcp-server/tools/response-budget.d.ts.map +1 -1
  33. package/dist/mcp-server/tools/response-budget.js +30 -21
  34. package/dist/mcp-server/tools/response-budget.js.map +1 -1
  35. package/dist/services/openalex/budget.d.ts +3 -1
  36. package/dist/services/openalex/budget.d.ts.map +1 -1
  37. package/dist/services/openalex/budget.js +3 -1
  38. package/dist/services/openalex/budget.js.map +1 -1
  39. package/dist/services/openalex/field-catalog.json +50 -1
  40. package/dist/services/openalex/filter-values.d.ts +46 -0
  41. package/dist/services/openalex/filter-values.d.ts.map +1 -0
  42. package/dist/services/openalex/filter-values.js +294 -0
  43. package/dist/services/openalex/filter-values.js.map +1 -0
  44. package/dist/services/openalex/iso-codes.json +1565 -0
  45. package/dist/services/openalex/openalex-service.d.ts +172 -20
  46. package/dist/services/openalex/openalex-service.d.ts.map +1 -1
  47. package/dist/services/openalex/openalex-service.js +560 -120
  48. package/dist/services/openalex/openalex-service.js.map +1 -1
  49. package/dist/services/openalex/provider-text.d.ts +7 -6
  50. package/dist/services/openalex/provider-text.d.ts.map +1 -1
  51. package/dist/services/openalex/provider-text.js +485 -61
  52. package/dist/services/openalex/provider-text.js.map +1 -1
  53. package/package.json +14 -12
  54. package/server.json +10 -24
@@ -0,0 +1,294 @@
1
+ /**
2
+ * @fileoverview A filter value in the form OpenAlex reads: country and `language` values
3
+ * converted to the codes OpenAlex stores; a value with spaces or a comma on a key OpenAlex matches
4
+ * as whole text — a name, city, region, raw affiliation string, award number, or `biblio` field —
5
+ * double-quoted, so its spaces and commas stay part of it; and the AND operator — a `+`, or on a
6
+ * key where words mean AND, a space between them — sent as the AND it spells. Values that are
7
+ * text rather than filter syntax keep their `+` literal: free-text search phrases, DOIs, dates,
8
+ * URLs, exactly matched names, `biblio` fields, award numbers, and any value the caller wrapped
9
+ * in quotes. A value that puts an AND operator beside a `|` (OR) fails, since OpenAlex splits on
10
+ * `|` first and drops every alternative that ANDs more than one value. A value sent double-quoted
11
+ * holds no `"` of its own, which would end the quoted phrase early and let the text after it
12
+ * parse as further filter clauses.
13
+ * @module services/openalex/filter-values
14
+ */
15
+ import { validationError } from '@cyanheads/mcp-ts-core/errors';
16
+ import isoCodes from './iso-codes.json' with { type: 'json' };
17
+ /**
18
+ * Lookups from the bundled ISO 3166-1 / ISO 639-2 table (`scripts/generate-iso-codes.ts`).
19
+ * Maps rather than the JSON objects, so a segment such as `constructor` or `__proto__` reads as
20
+ * no entry instead of an inherited member.
21
+ */
22
+ const COUNTRY_BY_ALPHA3 = new Map(Object.entries(isoCodes.countries.alpha3));
23
+ const COUNTRY_BY_NUMERIC = new Map(Object.entries(isoCodes.countries.numeric));
24
+ const COUNTRY_BY_NAME = new Map(Object.entries(isoCodes.countries.names));
25
+ const LANGUAGE_BY_ALPHA3 = new Map(Object.entries(isoCodes.languages.alpha3));
26
+ const LANGUAGE_BY_NAME = new Map(Object.entries(isoCodes.languages.names));
27
+ const TWO_LETTERS = /^[A-Za-z]{2}$/;
28
+ const THREE_LETTERS = /^[A-Za-z]{3}$/;
29
+ /** The most of a caller's text an error message quotes; the error's `data` keeps it whole. */
30
+ const ECHO_MAX_CHARS = 200;
31
+ /**
32
+ * Caller text as an error message quotes it: JSON-encoded, so a line break stays escaped, and
33
+ * capped, so caller-sized text cannot bloat the message.
34
+ */
35
+ export function quoteCallerText(text) {
36
+ return JSON.stringify(text.length > ECHO_MAX_CHARS ? `${text.slice(0, ECHO_MAX_CHARS)}…` : text);
37
+ }
38
+ /**
39
+ * Reject a double-quoted value, `sent` exactly as the clause sends it, when a `"` sits inside its
40
+ * outer pair (a leading `!` aside). OpenAlex ends a quoted phrase at the next `"` with no escape
41
+ * for one inside, so an interior quote closes the phrase early and the text after it reads as
42
+ * further filter clauses. Checking the value as sent, after any wrapping, is what catches a
43
+ * caller's quote pair the wrap encloses (`! "New York"` goes out as `"! "New York""`). Runs before
44
+ * any request.
45
+ */
46
+ function assertNoInteriorQuote(key, sent) {
47
+ const trimmed = sent.trim();
48
+ const body = trimmed.startsWith('!') ? trimmed.slice(1) : trimmed;
49
+ const inner = body.length >= 2 && body.startsWith('"') && body.endsWith('"') ? body.slice(1, -1) : body;
50
+ if (inner.includes('"')) {
51
+ throw validationError(`Filter ${quoteCallerText(key)} value is sent to OpenAlex as a quoted phrase and cannot contain a double quote of its own. Remove the double quote from the value.`, { reason: 'quote_in_filter_value', filterKey: key });
52
+ }
53
+ }
54
+ /**
55
+ * The key suffixes OpenAlex country-code fields end in, across every entity type — and nothing
56
+ * else: `countries_distinct_count` is a number.
57
+ */
58
+ const COUNTRY_KEY_SUFFIXES = ['country_code', 'country_codes', 'countries'];
59
+ /** A `.search` segment, at the end of the key or followed by a suffix such as `.exact`. */
60
+ const FREE_TEXT_SEARCH_KEY = /\.search(?:\.|$)/;
61
+ /**
62
+ * Whether `key` is a free-text search filter — `title.search`, `abstract.search.exact`,
63
+ * `title_abstract_keywords.search`, `semantic.search` — whose value is a phrase rather than
64
+ * filter syntax. `quotedCommaValue()` sends such a value holding a comma double-quoted, so the
65
+ * comma stays part of the phrase, and `upstreamFilterValue()` keeps its `+` literal.
66
+ */
67
+ function isFreeTextSearchKey(key) {
68
+ return FREE_TEXT_SEARCH_KEY.test(key);
69
+ }
70
+ /**
71
+ * Whether a `+` in this (hyphen-normalized) key's value is a literal character, because the value
72
+ * is text rather than filter syntax: a free-text search filter; a DOI filter (`doi`,
73
+ * `doi_starts_with`, `awards.doi`, `ids.doi`), whose values include SICI DOIs such as
74
+ * `10.1002/1097-0142(19940801)74:3+<1030::…>`; a date (every `_date` key), where the `+hh:mm` of
75
+ * a timestamp — which `from_created_date` and `to_updated_date` take, unlike `publication_date` —
76
+ * is its UTC offset; a URL
77
+ * (`primary_location.landing_page_url`), which can carry a `+` of its own — every `_url` key but a
78
+ * `has_` boolean such as `has_pdf_url`; a name matched exactly (`display_name`,
79
+ * `awards.funder_display_name`), such as the keyword `C++` or the funder `Erasmus+`; a `biblio`
80
+ * field, such as the combined issue `1+2`; and an award number as its funder deposited it
81
+ * (`awards.funder_award_id`).
82
+ */
83
+ function keepsPlusLiteral(field) {
84
+ if (isFreeTextSearchKey(field) || field.startsWith('biblio.'))
85
+ return true;
86
+ const leaf = field.slice(field.lastIndexOf('.') + 1);
87
+ return (leaf === 'doi' ||
88
+ leaf === 'doi_starts_with' ||
89
+ leaf === 'display_name' ||
90
+ leaf === 'funder_display_name' ||
91
+ leaf === 'funder_award_id' ||
92
+ leaf.endsWith('_date') ||
93
+ (leaf.endsWith('_url') && !leaf.startsWith('has_')));
94
+ }
95
+ /**
96
+ * The leaves of the (hyphen-normalized) keys whose values OpenAlex matches as whole text and
97
+ * stores with spaces: names (`display_name`, `awards.funder_display_name`), cities and regions
98
+ * (`geo.city`, `authorships.institutions.geo.region`), raw affiliation strings, award numbers as
99
+ * their funder deposited them (`DE AC02 05CH11231`), and an author's `block_key` (`j priem`).
100
+ * Every `biblio` field (issue `Supplement 1`) and `parsed_longest_name` part is such a key too.
101
+ */
102
+ const PHRASE_KEY_LEAVES = new Set([
103
+ 'display_name',
104
+ 'funder_display_name',
105
+ 'funder_award_id',
106
+ 'city',
107
+ 'region',
108
+ 'raw_affiliation_strings',
109
+ 'block_key',
110
+ ]);
111
+ function isPhraseKey(field) {
112
+ return (field.startsWith('biblio.') ||
113
+ field.startsWith('parsed_longest_name.') ||
114
+ PHRASE_KEY_LEAVES.has(field.slice(field.lastIndexOf('.') + 1)));
115
+ }
116
+ /** Whether `value` is already wrapped in double quotes, a leading `!` aside: `!"New York"`. */
117
+ function isQuoted(value) {
118
+ const body = value.startsWith('!') ? value.slice(1) : value;
119
+ return body.length >= 2 && body.startsWith('"') && body.endsWith('"');
120
+ }
121
+ /**
122
+ * The value as one double-quoted phrase, when a phrase key's value has a space between words and
123
+ * the caller has not quoted it; otherwise `undefined`. OpenAlex reads an unquoted space as AND,
124
+ * so `geo.city:New York` asks for a city named both `New` and `York` and matches nothing, while
125
+ * `geo.city:"New York"` matches the city. The quote goes around the whole value: OpenAlex strips
126
+ * it before reading `!` and `|`, so `"!New York"` excludes the city and `"New York|Boston"`
127
+ * matches either — and it reads a quote around a single `|` alternative as part of that
128
+ * alternative. It keeps edge whitespace inside the quotes (`" New York "` matches nothing), so the
129
+ * phrase is sent trimmed. A value with a `+` that means AND is left alone: no single clause ANDs a
130
+ * phrase with another value, quoted or not, so it goes out as it did before.
131
+ */
132
+ function quotedPhrase(field, value) {
133
+ if (!isPhraseKey(field))
134
+ return;
135
+ const phrase = value.trim();
136
+ if (!phrase.includes(' ') || isQuoted(phrase))
137
+ return;
138
+ if (phrase.includes('+') && !keepsPlusLiteral(field))
139
+ return;
140
+ return `"${phrase}"`;
141
+ }
142
+ /**
143
+ * A value holding a comma as its clause sends it, or `undefined` where the key takes no phrase
144
+ * and the comma could only read as OpenAlex's clause separator. A free-text search value or a
145
+ * phrase key's value goes out whole and double-quoted, as written — `|` still ORs inside the
146
+ * quotes, a leading `!` still negates the whole value, and a `+` is the literal character, which
147
+ * no clause could AND with a phrase anyway. A phrase key's value is trimmed first, as
148
+ * `quotedPhrase()` trims, and one the caller quoted (a leading `!` aside) is sent as written.
149
+ * Either way the value as sent cannot hold a `"` inside its outer pair, which would end the quoted
150
+ * phrase early (`quote_in_filter_value`).
151
+ */
152
+ export function quotedCommaValue(key, value) {
153
+ let sent;
154
+ if (isFreeTextSearchKey(key)) {
155
+ sent = value.startsWith('"') && value.endsWith('"') ? value : `"${value}"`;
156
+ }
157
+ else if (isPhraseKey(key.replaceAll('-', '_'))) {
158
+ const phrase = value.trim();
159
+ sent = isQuoted(phrase) ? phrase : `"${phrase}"`;
160
+ }
161
+ else {
162
+ return;
163
+ }
164
+ assertNoInteriorQuote(key, sent);
165
+ return sent;
166
+ }
167
+ /**
168
+ * One country segment as OpenAlex stores it: an alpha-3 code, numeric code, or English name —
169
+ * ISO's, or the one OpenAlex displays for the country (`Russia`, `Ivory Coast`) — becomes its
170
+ * alpha-2 code, and `UK` becomes `GB` (ISO 3166-1 reserves it for the United Kingdom). Two- and
171
+ * three-letter segments it does not map, `null`, empty segments, and the
172
+ * `countries/DE` path forms are sent as given — stored values include non-ISO two-letter codes
173
+ * (`XK`), MARC three-letter codes (`NYU`), and alpha-3 tags reachable only by path. Anything
174
+ * else cannot be a country code and matches nothing, so it fails here — a segment holding a space
175
+ * included, since a space is part of a name (`South Korea`) and never separates two codes
176
+ * (`DE US`), and the message says so.
177
+ */
178
+ function countrySegment(segment, key) {
179
+ if (segment === '' || segment === 'null' || segment.includes('/'))
180
+ return segment;
181
+ if (TWO_LETTERS.test(segment))
182
+ return segment.toUpperCase() === 'UK' ? 'GB' : segment;
183
+ if (THREE_LETTERS.test(segment))
184
+ return COUNTRY_BY_ALPHA3.get(segment.toUpperCase()) ?? segment;
185
+ const code = COUNTRY_BY_NUMERIC.get(segment) ?? COUNTRY_BY_NAME.get(segment.toLowerCase());
186
+ if (code !== undefined)
187
+ return code;
188
+ const spaced = segment.includes(' ')
189
+ ? ' A space is part of a country name ("South Korea"), never a separator: join codes with "|" for OR or "+" for AND.'
190
+ : '';
191
+ throw validationError(`Filter ${quoteCallerText(key)} value ${quoteCallerText(segment)} is not a country code. Country filters take ISO 3166-1 alpha-2 codes such as "DE"; an alpha-3 code ("DEU"), a numeric code ("276"), or an English country name ("Germany") is converted to one, and "null" matches records with no country.${spaced}`, { reason: 'invalid_country_code', filterKey: key, segment });
192
+ }
193
+ /**
194
+ * One `language` segment as OpenAlex stores it: an ISO 639-2 code with an ISO 639-1 equivalent
195
+ * becomes that code (`eng` → `en`), and an English language name becomes its code (`English` →
196
+ * `en`). Everything else is sent as given — stored values include three-letter codes with no
197
+ * ISO 639-1 equivalent (`ceb`), regional tags (`zh-cn`), and native names (`中文`) — so no
198
+ * language value is rejected.
199
+ */
200
+ function languageSegment(segment) {
201
+ if (TWO_LETTERS.test(segment))
202
+ return segment;
203
+ if (THREE_LETTERS.test(segment))
204
+ return LANGUAGE_BY_ALPHA3.get(segment.toLowerCase()) ?? segment;
205
+ return LANGUAGE_BY_NAME.get(segment.toLowerCase()) ?? segment;
206
+ }
207
+ /**
208
+ * Whether `value` puts a `|` beside a `+` that means AND, which no filter value can carry:
209
+ * OpenAlex splits a value on `|` first, and an alternative that ANDs more than one value matches
210
+ * nothing, so `DE|FR+US` answers with the German works alone. A `+` is no AND where
211
+ * `keepsPlusLiteral()`, nor inside a phrase key's value the caller wrapped in quotes, which
212
+ * OpenAlex reads as text split on `|`. Read from the caller's value before any conversion, so
213
+ * `Narnia|FR+US` fails for its operators, not its unknown country.
214
+ */
215
+ function mixesOrWithPlus(field, value) {
216
+ if (!value.includes('|') || !value.includes('+') || keepsPlusLiteral(field))
217
+ return false;
218
+ return !(isPhraseKey(field) && isQuoted(value.trim()));
219
+ }
220
+ /**
221
+ * Whether a `|` alternative of `value` holds a space where a space between words is the AND
222
+ * operator: on an ID or code key, `A1 A2|A3` drops to `A1|A3` (or matches nothing for a code)
223
+ * exactly as `A1+A2|A3` would. Read from the value as converted, where a country or `language`
224
+ * name is already its code: `South Korea|Japan` goes out as `KR|JP`, a multi-word country segment
225
+ * that is no name has failed as `invalid_country_code` (`DE US|FR`), and only a segment sent as
226
+ * given keeps its space — a `language` segment that names no language (`en fr|de`) or a country
227
+ * path form. Never on a phrase key, whose multi-word value is sent wholly double-quoted (its spaces
228
+ * literal), nor where `keepsPlusLiteral()`.
229
+ */
230
+ function mixesOrWithSpace(field, value) {
231
+ if (!value.includes('|') || keepsPlusLiteral(field) || isPhraseKey(field))
232
+ return false;
233
+ return value.split('|').some((alternative) => alternative.trim().includes(' '));
234
+ }
235
+ function mixedAndOrError(key) {
236
+ return validationError(`Filter ${quoteCallerText(key)} value mixes "|" (OR) with an AND operator — a "+" or a space — which one OpenAlex filter value cannot combine: OpenAlex splits the value on "|" first, and an alternative that ANDs more than one value matches nothing, so that alternative would be dropped without notice.`, { reason: 'mixed_and_or_in_filter_value', filterKey: key });
237
+ }
238
+ /**
239
+ * A filter value as its clause sends it. A value putting `|` beside an AND operator fails as
240
+ * `mixed_and_or_in_filter_value`: a `+` read from the caller's value (`mixesOrWithPlus()`), or a
241
+ * space on a key where words mean AND, read from the value as converted (`mixesOrWithSpace()`).
242
+ * On a country key or `language`, each `|`/`+` segment is trimmed and converted on its own, a
243
+ * leading `!` and the operators kept, so a multi-word name is one value (`South Korea|Japan` →
244
+ * `KR|JP`); upstream accepts a hyphenated key spelling, so `country-code` reads as
245
+ * `country_code`. A phrase key's multi-word value goes out as `quotedPhrase()` writes it. A value
246
+ * the caller wrapped in quotes goes out as written, its `+` a literal character. Otherwise, unless
247
+ * `keepsPlusLiteral()`, each `+` becomes a space: OpenAlex reads AND only from a decoded space — a
248
+ * raw `+` in the query string, which is what `URLSearchParams` sends for a space — and reads a
249
+ * `%2B` as a literal plus. Any value sent double-quoted is checked for a `"` of its own
250
+ * (`assertNoInteriorQuote()`).
251
+ */
252
+ export function upstreamFilterValue(key, value) {
253
+ const field = key.replaceAll('-', '_');
254
+ if (mixesOrWithPlus(field, value))
255
+ throw mixedAndOrError(key);
256
+ const convert = COUNTRY_KEY_SUFFIXES.some((suffix) => field.endsWith(suffix))
257
+ ? (segment) => countrySegment(segment, key)
258
+ : field === 'language'
259
+ ? languageSegment
260
+ : undefined;
261
+ const converted = convert === undefined
262
+ ? value
263
+ : value
264
+ .split(/([|+])/)
265
+ .map((part, index) => {
266
+ if (index % 2 === 1)
267
+ return part;
268
+ const trimmed = part.trim();
269
+ return trimmed.startsWith('!')
270
+ ? `!${convert(trimmed.slice(1).trim())}`
271
+ : convert(trimmed);
272
+ })
273
+ .join('');
274
+ if (mixesOrWithSpace(field, converted))
275
+ throw mixedAndOrError(key);
276
+ const phrase = quotedPhrase(field, converted);
277
+ if (phrase !== undefined) {
278
+ assertNoInteriorQuote(key, phrase);
279
+ return phrase;
280
+ }
281
+ /**
282
+ * A value the caller wrapped in quotes goes out as written, its `+` a literal character, and
283
+ * carries no `"` beyond that pair. Free-text search values are the exception: OpenAlex reads a
284
+ * `"` there as its own exact-phrase syntax, so a search value keeps its quotes and is left as-is.
285
+ */
286
+ if (!isFreeTextSearchKey(field) && isQuoted(converted.trim())) {
287
+ assertNoInteriorQuote(key, converted);
288
+ return converted;
289
+ }
290
+ if (keepsPlusLiteral(field))
291
+ return converted;
292
+ return converted.replaceAll('+', ' ');
293
+ }
294
+ //# sourceMappingURL=filter-values.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-values.js","sourceRoot":"","sources":["../../../src/services/openalex/filter-values.ts"],"names":[],"mappings":"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"}