@cyanheads/openalex-mcp-server 0.7.4 → 0.7.5
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 +2 -2
- package/CLAUDE.md +2 -2
- package/Dockerfile +1 -1
- package/LICENSE +1 -1
- package/README.md +7 -6
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/prompts/definitions/literature-review.prompt.d.ts +1 -1
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts +22 -11
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.js +20 -7
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts +19 -8
- package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/citation-graph.tool.js +26 -10
- package/dist/mcp-server/tools/definitions/citation-graph.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/describe-fields.tool.d.ts +5 -5
- package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts +30 -9
- package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/resolve-name.tool.js +30 -5
- package/dist/mcp-server/tools/definitions/resolve-name.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts +24 -13
- package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-entities.tool.js +26 -10
- package/dist/mcp-server/tools/definitions/search-entities.tool.js.map +1 -1
- package/dist/services/openalex/openalex-service.d.ts.map +1 -1
- package/dist/services/openalex/openalex-service.js +101 -20
- package/dist/services/openalex/openalex-service.js.map +1 -1
- package/dist/services/openalex/types.d.ts +7 -2
- package/dist/services/openalex/types.d.ts.map +1 -1
- package/package.json +9 -9
- package/server.json +3 -3
|
@@ -6,14 +6,14 @@ import { z } from '@cyanheads/mcp-ts-core';
|
|
|
6
6
|
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
7
|
export declare const resolveNameTool: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
8
8
|
entity_type: z.ZodOptional<z.ZodEnum<{
|
|
9
|
-
works: "works";
|
|
10
9
|
authors: "authors";
|
|
11
|
-
|
|
10
|
+
funders: "funders";
|
|
12
11
|
institutions: "institutions";
|
|
13
|
-
topics: "topics";
|
|
14
12
|
keywords: "keywords";
|
|
15
13
|
publishers: "publishers";
|
|
16
|
-
|
|
14
|
+
sources: "sources";
|
|
15
|
+
topics: "topics";
|
|
16
|
+
works: "works";
|
|
17
17
|
}>>;
|
|
18
18
|
query: z.ZodString;
|
|
19
19
|
filters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
@@ -30,9 +30,15 @@ export declare const resolveNameTool: import("@cyanheads/mcp-ts-core").ToolDefin
|
|
|
30
30
|
}, z.core.$strip>, readonly [{
|
|
31
31
|
readonly reason: "rate_limited";
|
|
32
32
|
readonly code: JsonRpcErrorCode.RateLimited;
|
|
33
|
-
readonly when: "OpenAlex throttled the autocomplete request (HTTP 429).";
|
|
33
|
+
readonly when: "OpenAlex throttled the autocomplete request for exceeding its per-second ceiling (HTTP 429).";
|
|
34
34
|
readonly retryable: true;
|
|
35
35
|
readonly recovery: "Wait several seconds and retry; consider lowering request frequency for this caller.";
|
|
36
|
+
}, {
|
|
37
|
+
readonly reason: "upstream_budget_exhausted";
|
|
38
|
+
readonly code: JsonRpcErrorCode.RateLimited;
|
|
39
|
+
readonly when: "The OpenAlex daily usage budget is spent (HTTP 429).";
|
|
40
|
+
readonly retryable: false;
|
|
41
|
+
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.";
|
|
36
42
|
}, {
|
|
37
43
|
readonly reason: "upstream_timeout";
|
|
38
44
|
readonly code: JsonRpcErrorCode.Timeout;
|
|
@@ -42,7 +48,7 @@ export declare const resolveNameTool: import("@cyanheads/mcp-ts-core").ToolDefin
|
|
|
42
48
|
}, {
|
|
43
49
|
readonly reason: "upstream_unavailable";
|
|
44
50
|
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
45
|
-
readonly when: "OpenAlex
|
|
51
|
+
readonly when: "OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON.";
|
|
46
52
|
readonly retryable: true;
|
|
47
53
|
readonly recovery: "Wait and retry; check https://openalex.org for service status if the outage persists.";
|
|
48
54
|
}, {
|
|
@@ -55,11 +61,26 @@ export declare const resolveNameTool: import("@cyanheads/mcp-ts-core").ToolDefin
|
|
|
55
61
|
readonly code: JsonRpcErrorCode.Forbidden;
|
|
56
62
|
readonly when: "OpenAlex denied access to autocomplete (HTTP 403).";
|
|
57
63
|
readonly recovery: "Confirm the API key has access to autocomplete, then retry the request.";
|
|
64
|
+
}, {
|
|
65
|
+
readonly reason: "comma_in_filter_value";
|
|
66
|
+
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
67
|
+
readonly when: "A `filters` value contains a comma, which collides with the OpenAlex filter separator.";
|
|
68
|
+
readonly recovery: "Use `|` for OR within a filter value (e.g. \"2020|2021\"), or move a free-text phrase containing commas into the `query` parameter.";
|
|
58
69
|
}, {
|
|
59
70
|
readonly reason: "upstream_invalid_params";
|
|
60
|
-
readonly code: JsonRpcErrorCode.
|
|
61
|
-
readonly when: "OpenAlex rejected the autocomplete query
|
|
62
|
-
readonly recovery: "
|
|
71
|
+
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
72
|
+
readonly when: "OpenAlex rejected an invalid filter field name on the autocomplete query (HTTP 400).";
|
|
73
|
+
readonly recovery: "The upstream message names the rejected field and suggests close matches. Use openalex_describe_fields(entity_type, \"filter\") to browse valid filter fields, or drop `filters` entirely.";
|
|
74
|
+
}, {
|
|
75
|
+
readonly reason: "upstream_invalid_id_value";
|
|
76
|
+
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
77
|
+
readonly when: "A `filters` entry expecting an entity ID received a value that is not an OpenAlex ID — usually a name (HTTP 400).";
|
|
78
|
+
readonly recovery: "Resolve that name to an OpenAlex ID first — run this tool without the ID-valued filter, take the `id` from a match, then re-run with the ID.";
|
|
79
|
+
}, {
|
|
80
|
+
readonly reason: "upstream_invalid_params_other";
|
|
81
|
+
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
82
|
+
readonly when: "OpenAlex rejected the autocomplete request (HTTP 400) for a reason other than an invalid field name.";
|
|
83
|
+
readonly recovery: "Read the upstream message in the error above and adjust the request — trim the query, check filter value formats, and ensure entity_type is a supported value.";
|
|
63
84
|
}, {
|
|
64
85
|
readonly reason: "upstream_validation_failed";
|
|
65
86
|
readonly code: JsonRpcErrorCode.ValidationError;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-name.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/resolve-name.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAIjE,eAAO,MAAM,eAAe
|
|
1
|
+
{"version":3,"file":"resolve-name.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/resolve-name.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAIjE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsM1B,CAAC"}
|
|
@@ -14,10 +14,17 @@ export const resolveNameTool = tool('openalex_resolve_name', {
|
|
|
14
14
|
{
|
|
15
15
|
reason: 'rate_limited',
|
|
16
16
|
code: JsonRpcErrorCode.RateLimited,
|
|
17
|
-
when: 'OpenAlex throttled the autocomplete request (HTTP 429).',
|
|
17
|
+
when: 'OpenAlex throttled the autocomplete request for exceeding its per-second ceiling (HTTP 429).',
|
|
18
18
|
retryable: true,
|
|
19
19
|
recovery: 'Wait several seconds and retry; consider lowering request frequency for this caller.',
|
|
20
20
|
},
|
|
21
|
+
{
|
|
22
|
+
reason: 'upstream_budget_exhausted',
|
|
23
|
+
code: JsonRpcErrorCode.RateLimited,
|
|
24
|
+
when: 'The OpenAlex daily usage budget is spent (HTTP 429).',
|
|
25
|
+
retryable: false,
|
|
26
|
+
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.',
|
|
27
|
+
},
|
|
21
28
|
{
|
|
22
29
|
reason: 'upstream_timeout',
|
|
23
30
|
code: JsonRpcErrorCode.Timeout,
|
|
@@ -28,7 +35,7 @@ export const resolveNameTool = tool('openalex_resolve_name', {
|
|
|
28
35
|
{
|
|
29
36
|
reason: 'upstream_unavailable',
|
|
30
37
|
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
31
|
-
when: 'OpenAlex
|
|
38
|
+
when: 'OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON.',
|
|
32
39
|
retryable: true,
|
|
33
40
|
recovery: 'Wait and retry; check https://openalex.org for service status if the outage persists.',
|
|
34
41
|
},
|
|
@@ -44,11 +51,29 @@ export const resolveNameTool = tool('openalex_resolve_name', {
|
|
|
44
51
|
when: 'OpenAlex denied access to autocomplete (HTTP 403).',
|
|
45
52
|
recovery: 'Confirm the API key has access to autocomplete, then retry the request.',
|
|
46
53
|
},
|
|
54
|
+
{
|
|
55
|
+
reason: 'comma_in_filter_value',
|
|
56
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
57
|
+
when: 'A `filters` value contains a comma, which collides with the OpenAlex filter separator.',
|
|
58
|
+
recovery: 'Use `|` for OR within a filter value (e.g. "2020|2021"), or move a free-text phrase containing commas into the `query` parameter.',
|
|
59
|
+
},
|
|
47
60
|
{
|
|
48
61
|
reason: 'upstream_invalid_params',
|
|
49
|
-
code: JsonRpcErrorCode.
|
|
50
|
-
when: 'OpenAlex rejected the autocomplete query
|
|
51
|
-
recovery: '
|
|
62
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
63
|
+
when: 'OpenAlex rejected an invalid filter field name on the autocomplete query (HTTP 400).',
|
|
64
|
+
recovery: 'The upstream message names the rejected field and suggests close matches. Use openalex_describe_fields(entity_type, "filter") to browse valid filter fields, or drop `filters` entirely.',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
reason: 'upstream_invalid_id_value',
|
|
68
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
69
|
+
when: 'A `filters` entry expecting an entity ID received a value that is not an OpenAlex ID — usually a name (HTTP 400).',
|
|
70
|
+
recovery: 'Resolve that name to an OpenAlex ID first — run this tool without the ID-valued filter, take the `id` from a match, then re-run with the ID.',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
reason: 'upstream_invalid_params_other',
|
|
74
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
75
|
+
when: 'OpenAlex rejected the autocomplete request (HTTP 400) for a reason other than an invalid field name.',
|
|
76
|
+
recovery: 'Read the upstream message in the error above and adjust the request — trim the query, check filter value formats, and ensure entity_type is a supported value.',
|
|
52
77
|
},
|
|
53
78
|
{
|
|
54
79
|
reason: 'upstream_validation_failed',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-name.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/resolve-name.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAE5D,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC,uBAAuB,EAAE;IAC3D,WAAW,EACT,mOAAmO;IACrO,SAAS,EACP,kHAAkH;IACpH,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;IAC9E,MAAM,EAAE;QACN;YACE,MAAM,EAAE,cAAc;YACtB,IAAI,EAAE,gBAAgB,CAAC,WAAW;YAClC,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"resolve-name.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/resolve-name.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,20 +6,20 @@ import { z } from '@cyanheads/mcp-ts-core';
|
|
|
6
6
|
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
7
|
export declare const searchEntitiesTool: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
8
8
|
entity_type: z.ZodEnum<{
|
|
9
|
-
works: "works";
|
|
10
9
|
authors: "authors";
|
|
11
|
-
|
|
10
|
+
funders: "funders";
|
|
12
11
|
institutions: "institutions";
|
|
13
|
-
topics: "topics";
|
|
14
12
|
keywords: "keywords";
|
|
15
13
|
publishers: "publishers";
|
|
16
|
-
|
|
14
|
+
sources: "sources";
|
|
15
|
+
topics: "topics";
|
|
16
|
+
works: "works";
|
|
17
17
|
}>;
|
|
18
18
|
id: z.ZodOptional<z.ZodString>;
|
|
19
19
|
query: z.ZodOptional<z.ZodString>;
|
|
20
20
|
search_mode: z.ZodDefault<z.ZodEnum<{
|
|
21
|
-
keyword: "keyword";
|
|
22
21
|
exact: "exact";
|
|
22
|
+
keyword: "keyword";
|
|
23
23
|
semantic: "semantic";
|
|
24
24
|
}>>;
|
|
25
25
|
filters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
@@ -37,7 +37,7 @@ export declare const searchEntitiesTool: import("@cyanheads/mcp-ts-core").ToolDe
|
|
|
37
37
|
}, z.core.$strip>;
|
|
38
38
|
results: z.ZodArray<z.ZodObject<{
|
|
39
39
|
id: z.ZodString;
|
|
40
|
-
display_name: z.ZodString
|
|
40
|
+
display_name: z.ZodNullable<z.ZodString>;
|
|
41
41
|
}, z.core.$loose>>;
|
|
42
42
|
}, z.core.$strip>, readonly [{
|
|
43
43
|
readonly reason: "semantic_per_page_cap";
|
|
@@ -62,9 +62,15 @@ export declare const searchEntitiesTool: import("@cyanheads/mcp-ts-core").ToolDe
|
|
|
62
62
|
}, {
|
|
63
63
|
readonly reason: "rate_limited";
|
|
64
64
|
readonly code: JsonRpcErrorCode.RateLimited;
|
|
65
|
-
readonly when: "OpenAlex throttled the request (HTTP 429).";
|
|
65
|
+
readonly when: "OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429).";
|
|
66
66
|
readonly retryable: true;
|
|
67
67
|
readonly recovery: "Wait several seconds and retry; consider lowering request frequency for this caller.";
|
|
68
|
+
}, {
|
|
69
|
+
readonly reason: "upstream_budget_exhausted";
|
|
70
|
+
readonly code: JsonRpcErrorCode.RateLimited;
|
|
71
|
+
readonly when: "The OpenAlex daily usage budget is spent (HTTP 429).";
|
|
72
|
+
readonly retryable: false;
|
|
73
|
+
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.";
|
|
68
74
|
}, {
|
|
69
75
|
readonly reason: "upstream_timeout";
|
|
70
76
|
readonly code: JsonRpcErrorCode.Timeout;
|
|
@@ -74,7 +80,7 @@ export declare const searchEntitiesTool: import("@cyanheads/mcp-ts-core").ToolDe
|
|
|
74
80
|
}, {
|
|
75
81
|
readonly reason: "upstream_unavailable";
|
|
76
82
|
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
77
|
-
readonly when: "OpenAlex
|
|
83
|
+
readonly when: "OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON.";
|
|
78
84
|
readonly retryable: true;
|
|
79
85
|
readonly recovery: "Wait and retry; check https://openalex.org for service status if the outage persists.";
|
|
80
86
|
}, {
|
|
@@ -89,24 +95,29 @@ export declare const searchEntitiesTool: import("@cyanheads/mcp-ts-core").ToolDe
|
|
|
89
95
|
readonly recovery: "Confirm the API key has access to this entity type or endpoint, then retry the request.";
|
|
90
96
|
}, {
|
|
91
97
|
readonly reason: "comma_in_filter_value";
|
|
92
|
-
readonly code: JsonRpcErrorCode.
|
|
98
|
+
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
93
99
|
readonly when: "A filter value contains a comma, which collides with the OpenAlex filter separator.";
|
|
94
100
|
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.";
|
|
95
101
|
}, {
|
|
96
102
|
readonly reason: "upstream_invalid_params";
|
|
97
|
-
readonly code: JsonRpcErrorCode.
|
|
103
|
+
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
98
104
|
readonly when: "OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400).";
|
|
99
105
|
readonly recovery: "The upstream message names the rejected field and suggests close matches. Use openalex_describe_fields(entity_type, context) to browse all valid fields for the given entity type and context.";
|
|
106
|
+
}, {
|
|
107
|
+
readonly reason: "upstream_invalid_id_value";
|
|
108
|
+
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
109
|
+
readonly when: "An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400).";
|
|
110
|
+
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, primary_topic.id, and cites accept IDs only.";
|
|
100
111
|
}, {
|
|
101
112
|
readonly reason: "upstream_sort_requires_search";
|
|
102
|
-
readonly code: JsonRpcErrorCode.
|
|
113
|
+
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
103
114
|
readonly when: "sort=-relevance_score was used without an active search (HTTP 400).";
|
|
104
115
|
readonly recovery: "Sorting by relevance_score requires an active search — add a `query` or a `*.search` filter (e.g. title.search), or choose a concrete sort field such as -cited_by_count or -publication_date.";
|
|
105
116
|
}, {
|
|
106
117
|
readonly reason: "upstream_invalid_params_other";
|
|
107
|
-
readonly code: JsonRpcErrorCode.
|
|
118
|
+
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
108
119
|
readonly when: "OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name.";
|
|
109
|
-
readonly recovery: "Read the upstream message in
|
|
120
|
+
readonly recovery: "Read the upstream message in the error above and adjust the request — check filter operators, value formats, and cursor/per_page bounds.";
|
|
110
121
|
}, {
|
|
111
122
|
readonly reason: "upstream_validation_failed";
|
|
112
123
|
readonly code: JsonRpcErrorCode.ValidationError;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-entities.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-entities.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAiCjE,eAAO,MAAM,kBAAkB
|
|
1
|
+
{"version":3,"file":"search-entities.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-entities.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAiCjE,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+U7B,CAAC"}
|
|
@@ -61,10 +61,17 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
61
61
|
{
|
|
62
62
|
reason: 'rate_limited',
|
|
63
63
|
code: JsonRpcErrorCode.RateLimited,
|
|
64
|
-
when: 'OpenAlex throttled the request (HTTP 429).',
|
|
64
|
+
when: 'OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429).',
|
|
65
65
|
retryable: true,
|
|
66
66
|
recovery: 'Wait several seconds and retry; consider lowering request frequency for this caller.',
|
|
67
67
|
},
|
|
68
|
+
{
|
|
69
|
+
reason: 'upstream_budget_exhausted',
|
|
70
|
+
code: JsonRpcErrorCode.RateLimited,
|
|
71
|
+
when: 'The OpenAlex daily usage budget is spent (HTTP 429).',
|
|
72
|
+
retryable: false,
|
|
73
|
+
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.',
|
|
74
|
+
},
|
|
68
75
|
{
|
|
69
76
|
reason: 'upstream_timeout',
|
|
70
77
|
code: JsonRpcErrorCode.Timeout,
|
|
@@ -75,7 +82,7 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
75
82
|
{
|
|
76
83
|
reason: 'upstream_unavailable',
|
|
77
84
|
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
78
|
-
when: 'OpenAlex
|
|
85
|
+
when: 'OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON.',
|
|
79
86
|
retryable: true,
|
|
80
87
|
recovery: 'Wait and retry; check https://openalex.org for service status if the outage persists.',
|
|
81
88
|
},
|
|
@@ -93,27 +100,33 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
93
100
|
},
|
|
94
101
|
{
|
|
95
102
|
reason: 'comma_in_filter_value',
|
|
96
|
-
code: JsonRpcErrorCode.
|
|
103
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
97
104
|
when: 'A filter value contains a comma, which collides with the OpenAlex filter separator.',
|
|
98
105
|
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.',
|
|
99
106
|
},
|
|
100
107
|
{
|
|
101
108
|
reason: 'upstream_invalid_params',
|
|
102
|
-
code: JsonRpcErrorCode.
|
|
109
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
103
110
|
when: 'OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400).',
|
|
104
111
|
recovery: 'The upstream message names the rejected field and suggests close matches. Use openalex_describe_fields(entity_type, context) to browse all valid fields for the given entity type and context.',
|
|
105
112
|
},
|
|
113
|
+
{
|
|
114
|
+
reason: 'upstream_invalid_id_value',
|
|
115
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
116
|
+
when: 'An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400).',
|
|
117
|
+
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, primary_topic.id, and cites accept IDs only.',
|
|
118
|
+
},
|
|
106
119
|
{
|
|
107
120
|
reason: 'upstream_sort_requires_search',
|
|
108
|
-
code: JsonRpcErrorCode.
|
|
121
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
109
122
|
when: 'sort=-relevance_score was used without an active search (HTTP 400).',
|
|
110
123
|
recovery: 'Sorting by relevance_score requires an active search — add a `query` or a `*.search` filter (e.g. title.search), or choose a concrete sort field such as -cited_by_count or -publication_date.',
|
|
111
124
|
},
|
|
112
125
|
{
|
|
113
126
|
reason: 'upstream_invalid_params_other',
|
|
114
|
-
code: JsonRpcErrorCode.
|
|
127
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
115
128
|
when: 'OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name.',
|
|
116
|
-
recovery: 'Read the upstream message in
|
|
129
|
+
recovery: 'Read the upstream message in the error above and adjust the request — check filter operators, value formats, and cursor/per_page bounds.',
|
|
117
130
|
},
|
|
118
131
|
{
|
|
119
132
|
reason: 'upstream_validation_failed',
|
|
@@ -143,7 +156,7 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
143
156
|
sort: z
|
|
144
157
|
.string()
|
|
145
158
|
.optional()
|
|
146
|
-
.describe('Sort field. Prefix with "-" for descending. Common: "cited_by_count", "-publication_date", "-relevance_score" (default when query present). Note: when combined with a keyword query, an explicit sort overrides relevance ranking entirely — top results may be highly cited but only tangentially on-topic. Use "-relevance_score" or omit sort to keep the most relevant results first. "-relevance_score" requires an active search via "query" or a "filter:search" filter — passing it without one will fail.'),
|
|
159
|
+
.describe('Sort field. Prefix with "-" for descending. Comma-separate for a multi-key sort, applied left to right, with the "-" prefix set per key ("-publication_year,cited_by_count" sorts by year descending, then citations ascending). Common: "cited_by_count", "-publication_date", "-relevance_score" (default when query present). Note: when combined with a keyword query, an explicit sort overrides relevance ranking entirely — top results may be highly cited but only tangentially on-topic. Use "-relevance_score" or omit sort to keep the most relevant results first. "-relevance_score" requires an active search via "query" or a "filter:search" filter — passing it without one will fail.'),
|
|
147
160
|
select: z
|
|
148
161
|
.array(z.string())
|
|
149
162
|
.optional()
|
|
@@ -186,10 +199,13 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
186
199
|
.array(z
|
|
187
200
|
.object({
|
|
188
201
|
id: z.string().describe('OpenAlex ID (e.g., "W2741809807", "A1234567890").'),
|
|
189
|
-
display_name: z
|
|
202
|
+
display_name: z
|
|
203
|
+
.string()
|
|
204
|
+
.nullable()
|
|
205
|
+
.describe('Entity name or work title. null when OpenAlex holds no title for the record (paratext works and other untitled entries) — use `id` to identify it.'),
|
|
190
206
|
})
|
|
191
207
|
.passthrough()
|
|
192
|
-
.describe('A single OpenAlex entity record.
|
|
208
|
+
.describe('A single OpenAlex entity record. `id` is always present and `display_name` is always returned (though it may be null); additional fields vary by entity_type and `select`.'))
|
|
193
209
|
.describe('OpenAlex entity objects passed through unchanged. Additional fields depend on entity_type and select.'),
|
|
194
210
|
}),
|
|
195
211
|
// Agent-facing context for the success path — the query/filters as parsed, the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-entities.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-entities.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAChF,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAqB,MAAM,8BAA8B,CAAC;AAE/E,MAAM,qBAAqB,GAAG,EAAE,CAAC;AACjC,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB,SAAS,eAAe,CAAC,KASxB;IACC,MAAM,KAAK,GAAG,CAAC,eAAe,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;IACnD,IAAI,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;IAC3C,IAAI,KAAK,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;IACtD,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACzD,KAAK,CAAC,IAAI,CAAC,eAAe,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;IACjD,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3D,KAAK,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,KAAK,CAAC,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACjD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACrE,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/D,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC,0BAA0B,EAAE;IACjE,WAAW,EACT,+iBAA+iB;IACjjB,SAAS,EACP,qHAAqH;IACvH,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;IAC9E,MAAM,EAAE;QACN;YACE,MAAM,EAAE,uBAAuB;YAC/B,IAAI,EAAE,gBAAgB,CAAC,eAAe;YACtC,IAAI,EAAE,+CAA+C,qBAAqB,GAAG;YAC7E,QAAQ,EAAE,sBAAsB,qBAAqB,6CAA6C;SACnG;QACD;YACE,MAAM,EAAE,oBAAoB;YAC5B,IAAI,EAAE,gBAAgB,CAAC,eAAe;YACtC,IAAI,EAAE,2CAA2C;YACjD,QAAQ,EAAE,0EAA0E;SACrF;QACD;YACE,MAAM,EAAE,qBAAqB;YAC7B,IAAI,EAAE,gBAAgB,CAAC,eAAe;YACtC,IAAI,EAAE,uCAAuC;YAC7C,QAAQ,EAAE,4DAA4D;SACvE;QACD;YACE,MAAM,EAAE,kBAAkB;YAC1B,IAAI,EAAE,gBAAgB,CAAC,QAAQ;YAC/B,IAAI,EAAE,0CAA0C;YAChD,QAAQ,EAAE,4EAA4E;SACvF;QACD;YACE,MAAM,EAAE,cAAc;YACtB,IAAI,EAAE,gBAAgB,CAAC,WAAW;YAClC,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"search-entities.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-entities.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openalex-service.d.ts","sourceRoot":"","sources":["../../../src/services/openalex/openalex-service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAsBtD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EAIvB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AAsDpB;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CA6C9C;
|
|
1
|
+
{"version":3,"file":"openalex-service.d.ts","sourceRoot":"","sources":["../../../src/services/openalex/openalex-service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAsBtD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EAIvB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AAsDpB;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CA6C9C;AA8gBD,cAAM,eAAe;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,cAKC;IAED,yFAAyF;IACzF,OAAO,CAAC,OAAO;IAyCf,OAAO,CAAC,2BAA2B;IAoEnC,OAAO,CAAC,aAAa;IAqCrB,sEAAsE;IAChE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CA0FtE;IAED,4BAA4B;IACtB,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAkDzE;IAED,oCAAoC;IAC9B,YAAY,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAsBxF;CACF;AAID,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED,wBAAgB,kBAAkB,IAAI,eAAe,CAIpD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAAC,UAAU,EAAE;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAE5F"}
|
|
@@ -407,16 +407,26 @@ const SORT_REQUIRES_SEARCH_RE = /must include a search query/i;
|
|
|
407
407
|
* no rejected field; the caller needs a categorical/year field, not a corrected name.
|
|
408
408
|
*/
|
|
409
409
|
const UNGROUPABLE_GROUP_BY_RE = /cannot group by/i;
|
|
410
|
+
/**
|
|
411
|
+
* OpenAlex 400 emitted when an entity-ID filter receives something that isn't an ID —
|
|
412
|
+
* typically a name: "'Albert' is not a valid OpenAlex ID." (upstream splits the value on
|
|
413
|
+
* whitespace before validating, so only the first token is named). The fix is to resolve
|
|
414
|
+
* the name to an ID first, which no other 400 shape's recovery says.
|
|
415
|
+
*/
|
|
416
|
+
const INVALID_ID_VALUE_RE = /is not a valid OpenAlex ID/i;
|
|
410
417
|
/**
|
|
411
418
|
* A single upstream HTTP 400 spans several distinct failure shapes, each needing a different
|
|
412
419
|
* caller recovery. Pick the declared tool reason from the message shape so the per-tool
|
|
413
420
|
* `recovery` hint (resolved via `ctx.recoveryFor`) matches the actual failure instead of
|
|
414
|
-
* always claiming a rejected field name. Ordering: the
|
|
415
|
-
*
|
|
416
|
-
*
|
|
421
|
+
* always claiming a rejected field name. Ordering: the ID-value check runs first because it
|
|
422
|
+
* is the only shape naming a concrete upstream concept; the field-name check is anchored at
|
|
423
|
+
* string start; the rest are keyword probes; anything unmatched falls through to a neutral
|
|
424
|
+
* `_other` reason so no shape inherits the field-name recovery by default.
|
|
417
425
|
*/
|
|
418
426
|
function classifyInvalidParamsReason(rawMessage) {
|
|
419
427
|
if (rawMessage !== undefined) {
|
|
428
|
+
if (INVALID_ID_VALUE_RE.test(rawMessage))
|
|
429
|
+
return 'upstream_invalid_id_value';
|
|
420
430
|
if (REJECTED_FIELD_RE.test(rawMessage))
|
|
421
431
|
return 'upstream_invalid_params';
|
|
422
432
|
if (SORT_REQUIRES_SEARCH_RE.test(rawMessage))
|
|
@@ -426,6 +436,55 @@ function classifyInvalidParamsReason(rawMessage) {
|
|
|
426
436
|
}
|
|
427
437
|
return 'upstream_invalid_params_other';
|
|
428
438
|
}
|
|
439
|
+
/** Reason for a 429 caused by daily-budget exhaustion rather than burst throttling. */
|
|
440
|
+
const BUDGET_EXHAUSTED_REASON = 'upstream_budget_exhausted';
|
|
441
|
+
/**
|
|
442
|
+
* OpenAlex returns 429 for two unrelated conditions: bursting past its per-second ceiling,
|
|
443
|
+
* and spending the daily usage budget. The recoveries are opposites — one clears in seconds,
|
|
444
|
+
* the other not until the midnight-UTC reset — so they need separate reasons.
|
|
445
|
+
*
|
|
446
|
+
* The literal budget-exhaustion body is not published in OpenAlex's docs, so the match is
|
|
447
|
+
* deliberately loose: several independent signals, none of them load-bearing on an exact
|
|
448
|
+
* wording. The burst-throttle bodies carry none of these tokens.
|
|
449
|
+
*/
|
|
450
|
+
const BUDGET_EXHAUSTED_RE = /insufficient budget|resets at midnight|budget[^.]*remaining/i;
|
|
451
|
+
/** Discriminate the two upstream 429 shapes on the message; default to burst throttling. */
|
|
452
|
+
function classifyRateLimitReason(rawMessage) {
|
|
453
|
+
if (rawMessage !== undefined && BUDGET_EXHAUSTED_RE.test(rawMessage)) {
|
|
454
|
+
return BUDGET_EXHAUSTED_REASON;
|
|
455
|
+
}
|
|
456
|
+
return 'rate_limited';
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Pick the reason for a normalized throw. Two upstream statuses carry several distinct
|
|
460
|
+
* failure shapes under one error code — 400 (rejected field / relevance sort without a
|
|
461
|
+
* search / ungroupable group_by / non-ID filter value) and 429 (burst throttle vs. spent
|
|
462
|
+
* daily budget) — and each shape needs its own caller recovery, so both discriminate on the
|
|
463
|
+
* upstream message. Every other code maps to a single reason.
|
|
464
|
+
*/
|
|
465
|
+
function resolveThrowReason(code, mappedReason, rawMessage) {
|
|
466
|
+
if (code === JsonRpcErrorCode.InvalidParams)
|
|
467
|
+
return classifyInvalidParamsReason(rawMessage);
|
|
468
|
+
if (code === JsonRpcErrorCode.RateLimited)
|
|
469
|
+
return classifyRateLimitReason(rawMessage);
|
|
470
|
+
return mappedReason;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Domain-language replacement for the framework's fetch-plumbing message on the failures
|
|
474
|
+
* that never carry an HTTP status. `fetch GET <url> timed out.` describes this client, not
|
|
475
|
+
* the upstream; the caller needs to read that OpenAlex did not answer.
|
|
476
|
+
*/
|
|
477
|
+
function domainMessageForFetchFailure(error, path) {
|
|
478
|
+
switch (error.data?.errorSource) {
|
|
479
|
+
case 'FetchTimeout':
|
|
480
|
+
return `OpenAlex did not respond within ${REQUEST_TIMEOUT_MS / 1000}s for ${path}`;
|
|
481
|
+
case 'FetchNetworkError':
|
|
482
|
+
case 'FetchNetworkErrorWrapper':
|
|
483
|
+
return `Could not reach the OpenAlex API for ${path}`;
|
|
484
|
+
default:
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
429
488
|
/**
|
|
430
489
|
* Context keywords in the upstream 400 message mapped to the catalog lookup key.
|
|
431
490
|
* OpenAlex uses "filter" and "group_by" / "select" in its messages.
|
|
@@ -477,14 +536,30 @@ function getSearchParamKey(searchMode) {
|
|
|
477
536
|
return 'search';
|
|
478
537
|
}
|
|
479
538
|
}
|
|
539
|
+
/**
|
|
540
|
+
* Translate one sort key to OpenAlex syntax: a leading `-` becomes a `:desc` suffix, and a
|
|
541
|
+
* bare `relevance_score` is coerced to descending (ascending relevance is never the intent).
|
|
542
|
+
* Anything else — including a key that already carries `:desc` — passes through.
|
|
543
|
+
*/
|
|
544
|
+
function normalizeSortKey(key) {
|
|
545
|
+
if (key.startsWith('-'))
|
|
546
|
+
return `${key.slice(1)}:desc`;
|
|
547
|
+
if (key === 'relevance_score')
|
|
548
|
+
return 'relevance_score:desc';
|
|
549
|
+
return key;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* OpenAlex accepts comma-separated sort keys (`publication_year:desc,cited_by_count`), so
|
|
553
|
+
* each key is normalized independently. Treating the value as a single field moved the
|
|
554
|
+
* descending marker onto the last key, silently flipping the dash-prefixed one to ascending.
|
|
555
|
+
*/
|
|
480
556
|
function normalizeSort(sort) {
|
|
481
557
|
if (!sort)
|
|
482
558
|
return;
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
return sort;
|
|
559
|
+
return sort
|
|
560
|
+
.split(',')
|
|
561
|
+
.map((key) => normalizeSortKey(key.trim()))
|
|
562
|
+
.join(',');
|
|
488
563
|
}
|
|
489
564
|
function toRequestContext(ctx, operation) {
|
|
490
565
|
return {
|
|
@@ -552,12 +627,18 @@ class OpenAlexService {
|
|
|
552
627
|
throw error;
|
|
553
628
|
}
|
|
554
629
|
const statusCode = typeof error.data?.statusCode === 'number' ? error.data.statusCode : undefined;
|
|
555
|
-
|
|
630
|
+
// Status-mapped failures classify off the HTTP status. Everything else — the client-side
|
|
631
|
+
// `REQUEST_TIMEOUT_MS` abort, DNS/connection failures, and the `parseResponse` throws
|
|
632
|
+
// (empty body, HTML, invalid JSON) — never carries a status, so the error's own code is
|
|
633
|
+
// the signal. Routing both through the same table means the transient paths, which are
|
|
634
|
+
// the slowest to fail, arrive with the reason and recovery hint their contract declares
|
|
635
|
+
// instead of an unclassified re-throw.
|
|
636
|
+
const code = statusCode === undefined ? error.code : httpStatusToErrorCode(statusCode);
|
|
556
637
|
const normalized = code === undefined ? undefined : NORMALIZED_THROW_BY_CODE[code];
|
|
557
|
-
if (normalized === undefined) {
|
|
558
|
-
//
|
|
559
|
-
// `Fetch failed for <URL>. Status: …` — the URL
|
|
560
|
-
// the `mailto` identifier. Redact before bubbling.
|
|
638
|
+
if (code === undefined || normalized === undefined) {
|
|
639
|
+
// No mapped reason for this code (a caller-side abort, an upstream 500). Framework
|
|
640
|
+
// fetch errors format the message as `Fetch failed for <URL>. Status: …` — the URL
|
|
641
|
+
// carries the `api_key` credential and the `mailto` identifier. Redact before bubbling.
|
|
561
642
|
throw new McpError(error.code, redactUrlsInMessage(error.message), error.data, {
|
|
562
643
|
cause: error,
|
|
563
644
|
});
|
|
@@ -565,13 +646,7 @@ class OpenAlexService {
|
|
|
565
646
|
const { factory } = normalized;
|
|
566
647
|
const upstream = parseOpenAlexErrorBody(error.data?.responseBody);
|
|
567
648
|
const rawMessage = upstream?.message ?? upstream?.error;
|
|
568
|
-
|
|
569
|
-
// shape — discriminate on the message so the tool's `recovery` hint matches (invalid field
|
|
570
|
-
// name → describe_fields; relevance sort without a search → add a query; ungroupable
|
|
571
|
-
// group_by → pick a categorical field). Non-400 codes keep their single mapped reason.
|
|
572
|
-
const reason = code === JsonRpcErrorCode.InvalidParams
|
|
573
|
-
? classifyInvalidParamsReason(rawMessage)
|
|
574
|
-
: normalized.reason;
|
|
649
|
+
const reason = resolveThrowReason(code, normalized.reason, rawMessage);
|
|
575
650
|
// A truncated body's appended valid-fields list is misleading. For an invalid-field
|
|
576
651
|
// 400, replace it with catalog-backed ranked suggestions; otherwise strip it. The two
|
|
577
652
|
// are mutually exclusive — appendFieldSuggestions already drops the partial list, so it
|
|
@@ -587,6 +662,7 @@ class OpenAlexService {
|
|
|
587
662
|
: withSuggestions;
|
|
588
663
|
}
|
|
589
664
|
const message = sanitizedMessage ??
|
|
665
|
+
domainMessageForFetchFailure(error, path) ??
|
|
590
666
|
(code === JsonRpcErrorCode.NotFound
|
|
591
667
|
? `Entity not found at ${path}`
|
|
592
668
|
: redactUrlsInMessage(error.message));
|
|
@@ -595,6 +671,11 @@ class OpenAlexService {
|
|
|
595
671
|
path,
|
|
596
672
|
reason,
|
|
597
673
|
...ctx.recoveryFor(reason),
|
|
674
|
+
// `RateLimited` sits in the framework's transient set, so `withRetry` would spend the
|
|
675
|
+
// full attempt budget against a budget wall that stays up until the daily reset. This
|
|
676
|
+
// flag is the framework's per-error opt-out from that loop; the contract's own
|
|
677
|
+
// `retryable` field only informs the client and cannot stop our own retries.
|
|
678
|
+
...(reason === BUDGET_EXHAUSTED_REASON ? { retryable: false } : {}),
|
|
598
679
|
...(upstream?.error ? { upstreamError: upstream.error } : {}),
|
|
599
680
|
...(upstream?.message ? { upstreamMessage: upstream.message } : {}),
|
|
600
681
|
};
|