@cyanheads/openalex-mcp-server 0.7.4 → 0.7.6
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 +8 -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 +27 -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 +34 -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 +24 -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 +40 -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 +35 -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 +49 -6
- package/dist/mcp-server/tools/definitions/resolve-name.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts +29 -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 +40 -10
- package/dist/mcp-server/tools/definitions/search-entities.tool.js.map +1 -1
- package/dist/mcp-server/tools/render-budget.d.ts +14 -0
- package/dist/mcp-server/tools/render-budget.d.ts.map +1 -0
- package/dist/mcp-server/tools/render-budget.js +31 -0
- package/dist/mcp-server/tools/render-budget.js.map +1 -0
- package/dist/services/openalex/budget.d.ts +46 -0
- package/dist/services/openalex/budget.d.ts.map +1 -0
- package/dist/services/openalex/budget.js +51 -0
- package/dist/services/openalex/budget.js.map +1 -0
- package/dist/services/openalex/openalex-service.d.ts.map +1 -1
- package/dist/services/openalex/openalex-service.js +145 -36
- 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
|
@@ -5,19 +5,19 @@
|
|
|
5
5
|
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
6
|
export declare const describeFieldsTool: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
7
7
|
entity_type: z.ZodEnum<{
|
|
8
|
-
works: "works";
|
|
9
8
|
authors: "authors";
|
|
10
|
-
|
|
9
|
+
funders: "funders";
|
|
11
10
|
institutions: "institutions";
|
|
12
|
-
topics: "topics";
|
|
13
11
|
keywords: "keywords";
|
|
14
12
|
publishers: "publishers";
|
|
15
|
-
|
|
13
|
+
sources: "sources";
|
|
14
|
+
topics: "topics";
|
|
15
|
+
works: "works";
|
|
16
16
|
}>;
|
|
17
17
|
context: z.ZodEnum<{
|
|
18
18
|
filter: "filter";
|
|
19
|
-
select: "select";
|
|
20
19
|
group_by: "group_by";
|
|
20
|
+
select: "select";
|
|
21
21
|
}>;
|
|
22
22
|
query: z.ZodOptional<z.ZodString>;
|
|
23
23
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -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;
|
|
@@ -67,5 +88,10 @@ export declare const resolveNameTool: import("@cyanheads/mcp-ts-core").ToolDefin
|
|
|
67
88
|
readonly recovery: "Read the upstream message for the specific field, then adjust the request to satisfy validation.";
|
|
68
89
|
}], {
|
|
69
90
|
readonly notice: z.ZodOptional<z.ZodString>;
|
|
91
|
+
readonly budget: z.ZodOptional<z.ZodObject<{
|
|
92
|
+
costUsd: z.ZodNumber;
|
|
93
|
+
remainingUsd: z.ZodNumber;
|
|
94
|
+
resetsInSeconds: z.ZodNumber;
|
|
95
|
+
}, z.core.$strip>>;
|
|
70
96
|
}>;
|
|
71
97
|
//# sourceMappingURL=resolve-name.tool.d.ts.map
|
|
@@ -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;
|
|
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;AAKjE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4N1B,CAAC"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
6
|
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { renderBudgetTrailer } from '../../../mcp-server/tools/render-budget.js';
|
|
7
8
|
import { getOpenAlexService } from '../../../services/openalex/openalex-service.js';
|
|
8
9
|
import { ENTITY_TYPES } from '../../../services/openalex/types.js';
|
|
9
10
|
export const resolveNameTool = tool('openalex_resolve_name', {
|
|
@@ -14,10 +15,17 @@ export const resolveNameTool = tool('openalex_resolve_name', {
|
|
|
14
15
|
{
|
|
15
16
|
reason: 'rate_limited',
|
|
16
17
|
code: JsonRpcErrorCode.RateLimited,
|
|
17
|
-
when: 'OpenAlex throttled the autocomplete request (HTTP 429).',
|
|
18
|
+
when: 'OpenAlex throttled the autocomplete request for exceeding its per-second ceiling (HTTP 429).',
|
|
18
19
|
retryable: true,
|
|
19
20
|
recovery: 'Wait several seconds and retry; consider lowering request frequency for this caller.',
|
|
20
21
|
},
|
|
22
|
+
{
|
|
23
|
+
reason: 'upstream_budget_exhausted',
|
|
24
|
+
code: JsonRpcErrorCode.RateLimited,
|
|
25
|
+
when: 'The OpenAlex daily usage budget is spent (HTTP 429).',
|
|
26
|
+
retryable: false,
|
|
27
|
+
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.',
|
|
28
|
+
},
|
|
21
29
|
{
|
|
22
30
|
reason: 'upstream_timeout',
|
|
23
31
|
code: JsonRpcErrorCode.Timeout,
|
|
@@ -28,7 +36,7 @@ export const resolveNameTool = tool('openalex_resolve_name', {
|
|
|
28
36
|
{
|
|
29
37
|
reason: 'upstream_unavailable',
|
|
30
38
|
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
31
|
-
when: 'OpenAlex
|
|
39
|
+
when: 'OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON.',
|
|
32
40
|
retryable: true,
|
|
33
41
|
recovery: 'Wait and retry; check https://openalex.org for service status if the outage persists.',
|
|
34
42
|
},
|
|
@@ -44,11 +52,29 @@ export const resolveNameTool = tool('openalex_resolve_name', {
|
|
|
44
52
|
when: 'OpenAlex denied access to autocomplete (HTTP 403).',
|
|
45
53
|
recovery: 'Confirm the API key has access to autocomplete, then retry the request.',
|
|
46
54
|
},
|
|
55
|
+
{
|
|
56
|
+
reason: 'comma_in_filter_value',
|
|
57
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
58
|
+
when: 'A `filters` value contains a comma, which collides with the OpenAlex filter separator.',
|
|
59
|
+
recovery: 'Use `|` for OR within a filter value (e.g. "2020|2021"), or move a free-text phrase containing commas into the `query` parameter.',
|
|
60
|
+
},
|
|
47
61
|
{
|
|
48
62
|
reason: 'upstream_invalid_params',
|
|
49
|
-
code: JsonRpcErrorCode.
|
|
50
|
-
when: 'OpenAlex rejected the autocomplete query
|
|
51
|
-
recovery: '
|
|
63
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
64
|
+
when: 'OpenAlex rejected an invalid filter field name on the autocomplete query (HTTP 400).',
|
|
65
|
+
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.',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
reason: 'upstream_invalid_id_value',
|
|
69
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
70
|
+
when: 'A `filters` entry expecting an entity ID received a value that is not an OpenAlex ID — usually a name (HTTP 400).',
|
|
71
|
+
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.',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
reason: 'upstream_invalid_params_other',
|
|
75
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
76
|
+
when: 'OpenAlex rejected the autocomplete request (HTTP 400) for a reason other than an invalid field name.',
|
|
77
|
+
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
78
|
},
|
|
53
79
|
{
|
|
54
80
|
reason: 'upstream_validation_failed',
|
|
@@ -100,12 +126,29 @@ export const resolveNameTool = tool('openalex_resolve_name', {
|
|
|
100
126
|
.describe('Autocomplete matches, up to 10.'),
|
|
101
127
|
}),
|
|
102
128
|
// Agent-facing success-path context — a notice when the query matched nothing so the
|
|
103
|
-
// caller sees explicit guidance rather than a silent empty array
|
|
129
|
+
// caller sees explicit guidance rather than a silent empty array, plus the OpenAlex budget
|
|
130
|
+
// reading. This tool is the documented first step of most workflows, so its trailer is
|
|
131
|
+
// where a caller learns what today's budget looks like before committing to a sweep.
|
|
104
132
|
enrichment: {
|
|
105
133
|
notice: z
|
|
106
134
|
.string()
|
|
107
135
|
.optional()
|
|
108
136
|
.describe('Recovery guidance when no matches were found — echoes the query and suggests corrections. Absent when results are present.'),
|
|
137
|
+
budget: z
|
|
138
|
+
.object({
|
|
139
|
+
costUsd: z
|
|
140
|
+
.number()
|
|
141
|
+
.describe('USD this call spent. Autocomplete is priced at the floor — resolving a name before filtering costs far less than the failed searches an ambiguous name causes.'),
|
|
142
|
+
remainingUsd: z.number().describe("USD left in today's OpenAlex budget after this call."),
|
|
143
|
+
resetsInSeconds: z
|
|
144
|
+
.number()
|
|
145
|
+
.describe('Seconds until the daily budget refills (midnight UTC).'),
|
|
146
|
+
})
|
|
147
|
+
.optional()
|
|
148
|
+
.describe('What this call cost against the OpenAlex daily budget and what is left of it. Read `remainingUsd` here to size the search or traversal this resolution feeds. Absent when OpenAlex omitted the accounting headers.'),
|
|
149
|
+
},
|
|
150
|
+
enrichmentTrailer: {
|
|
151
|
+
budget: { render: renderBudgetTrailer },
|
|
109
152
|
},
|
|
110
153
|
async handler(input, ctx) {
|
|
111
154
|
const service = getOpenAlexService();
|
|
@@ -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;
|
|
@@ -116,5 +127,10 @@ export declare const searchEntitiesTool: import("@cyanheads/mcp-ts-core").ToolDe
|
|
|
116
127
|
readonly echo: z.ZodString;
|
|
117
128
|
readonly totalCount: z.ZodNumber;
|
|
118
129
|
readonly notice: z.ZodOptional<z.ZodString>;
|
|
130
|
+
readonly budget: z.ZodOptional<z.ZodObject<{
|
|
131
|
+
costUsd: z.ZodNumber;
|
|
132
|
+
remainingUsd: z.ZodNumber;
|
|
133
|
+
resetsInSeconds: z.ZodNumber;
|
|
134
|
+
}, z.core.$strip>>;
|
|
119
135
|
}>;
|
|
120
136
|
//# sourceMappingURL=search-entities.tool.d.ts.map
|
|
@@ -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;
|
|
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;AAkCjE,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgW7B,CAAC"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
6
|
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { renderBudgetTrailer } from '../../../mcp-server/tools/render-budget.js';
|
|
7
8
|
import { renderEntityRecord } from '../../../mcp-server/tools/render-entity-record.js';
|
|
8
9
|
import { getOpenAlexService } from '../../../services/openalex/openalex-service.js';
|
|
9
10
|
import { ENTITY_TYPES } from '../../../services/openalex/types.js';
|
|
@@ -61,10 +62,17 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
61
62
|
{
|
|
62
63
|
reason: 'rate_limited',
|
|
63
64
|
code: JsonRpcErrorCode.RateLimited,
|
|
64
|
-
when: 'OpenAlex throttled the request (HTTP 429).',
|
|
65
|
+
when: 'OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429).',
|
|
65
66
|
retryable: true,
|
|
66
67
|
recovery: 'Wait several seconds and retry; consider lowering request frequency for this caller.',
|
|
67
68
|
},
|
|
69
|
+
{
|
|
70
|
+
reason: 'upstream_budget_exhausted',
|
|
71
|
+
code: JsonRpcErrorCode.RateLimited,
|
|
72
|
+
when: 'The OpenAlex daily usage budget is spent (HTTP 429).',
|
|
73
|
+
retryable: false,
|
|
74
|
+
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.',
|
|
75
|
+
},
|
|
68
76
|
{
|
|
69
77
|
reason: 'upstream_timeout',
|
|
70
78
|
code: JsonRpcErrorCode.Timeout,
|
|
@@ -75,7 +83,7 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
75
83
|
{
|
|
76
84
|
reason: 'upstream_unavailable',
|
|
77
85
|
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
78
|
-
when: 'OpenAlex
|
|
86
|
+
when: 'OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON.',
|
|
79
87
|
retryable: true,
|
|
80
88
|
recovery: 'Wait and retry; check https://openalex.org for service status if the outage persists.',
|
|
81
89
|
},
|
|
@@ -93,27 +101,33 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
93
101
|
},
|
|
94
102
|
{
|
|
95
103
|
reason: 'comma_in_filter_value',
|
|
96
|
-
code: JsonRpcErrorCode.
|
|
104
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
97
105
|
when: 'A filter value contains a comma, which collides with the OpenAlex filter separator.',
|
|
98
106
|
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
107
|
},
|
|
100
108
|
{
|
|
101
109
|
reason: 'upstream_invalid_params',
|
|
102
|
-
code: JsonRpcErrorCode.
|
|
110
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
103
111
|
when: 'OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400).',
|
|
104
112
|
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
113
|
},
|
|
114
|
+
{
|
|
115
|
+
reason: 'upstream_invalid_id_value',
|
|
116
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
117
|
+
when: 'An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400).',
|
|
118
|
+
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.',
|
|
119
|
+
},
|
|
106
120
|
{
|
|
107
121
|
reason: 'upstream_sort_requires_search',
|
|
108
|
-
code: JsonRpcErrorCode.
|
|
122
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
109
123
|
when: 'sort=-relevance_score was used without an active search (HTTP 400).',
|
|
110
124
|
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
125
|
},
|
|
112
126
|
{
|
|
113
127
|
reason: 'upstream_invalid_params_other',
|
|
114
|
-
code: JsonRpcErrorCode.
|
|
128
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
115
129
|
when: 'OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name.',
|
|
116
|
-
recovery: 'Read the upstream message in
|
|
130
|
+
recovery: 'Read the upstream message in the error above and adjust the request — check filter operators, value formats, and cursor/per_page bounds.',
|
|
117
131
|
},
|
|
118
132
|
{
|
|
119
133
|
reason: 'upstream_validation_failed',
|
|
@@ -143,7 +157,7 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
143
157
|
sort: z
|
|
144
158
|
.string()
|
|
145
159
|
.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.'),
|
|
160
|
+
.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
161
|
select: z
|
|
148
162
|
.array(z.string())
|
|
149
163
|
.optional()
|
|
@@ -186,10 +200,13 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
186
200
|
.array(z
|
|
187
201
|
.object({
|
|
188
202
|
id: z.string().describe('OpenAlex ID (e.g., "W2741809807", "A1234567890").'),
|
|
189
|
-
display_name: z
|
|
203
|
+
display_name: z
|
|
204
|
+
.string()
|
|
205
|
+
.nullable()
|
|
206
|
+
.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
207
|
})
|
|
191
208
|
.passthrough()
|
|
192
|
-
.describe('A single OpenAlex entity record.
|
|
209
|
+
.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
210
|
.describe('OpenAlex entity objects passed through unchanged. Additional fields depend on entity_type and select.'),
|
|
194
211
|
}),
|
|
195
212
|
// Agent-facing context for the success path — the query/filters as parsed, the
|
|
@@ -204,10 +221,23 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
204
221
|
.string()
|
|
205
222
|
.optional()
|
|
206
223
|
.describe('Recovery guidance when results are empty — echoes the criteria and suggests how to broaden. Absent on successful result pages.'),
|
|
224
|
+
budget: z
|
|
225
|
+
.object({
|
|
226
|
+
costUsd: z
|
|
227
|
+
.number()
|
|
228
|
+
.describe('USD this call spent. 0 for an `id` lookup — OpenAlex does not bill single-entity fetches, so batching known IDs beats paging a filtered list.'),
|
|
229
|
+
remainingUsd: z.number().describe("USD left in today's OpenAlex budget after this call."),
|
|
230
|
+
resetsInSeconds: z
|
|
231
|
+
.number()
|
|
232
|
+
.describe('Seconds until the daily budget refills (midnight UTC).'),
|
|
233
|
+
})
|
|
234
|
+
.optional()
|
|
235
|
+
.describe('What this call cost against the OpenAlex daily budget and what is left of it. Price a full traversal before committing to it: `totalCount` ÷ `per_page` × `costUsd` against `remainingUsd`. Absent when OpenAlex omitted the accounting headers.'),
|
|
207
236
|
},
|
|
208
237
|
enrichmentTrailer: {
|
|
209
238
|
echo: { label: 'Query' },
|
|
210
239
|
totalCount: { label: 'Total' },
|
|
240
|
+
budget: { render: renderBudgetTrailer },
|
|
211
241
|
},
|
|
212
242
|
async handler(input, ctx) {
|
|
213
243
|
if (input.search_mode === 'semantic' && input.per_page > SEMANTIC_PER_PAGE_CAP) {
|
|
@@ -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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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Markdown rendering for the `budget` enrichment field — shared by the
|
|
3
|
+
* `enrichmentTrailer` of every tool that calls the OpenAlex API. Collapses the three
|
|
4
|
+
* numeric fields to one trailer line; `structuredContent` keeps the numbers.
|
|
5
|
+
* @module mcp-server/tools/render-budget
|
|
6
|
+
*/
|
|
7
|
+
import type { UpstreamBudget } from '../../services/openalex/budget.js';
|
|
8
|
+
/**
|
|
9
|
+
* One-line budget trailer. `costUsd: 0` renders as "free" rather than `$0.0000` because it
|
|
10
|
+
* carries real signal — it means the call was a singleton ID lookup, which OpenAlex does not
|
|
11
|
+
* bill, so batching by ID beats paging a filtered list.
|
|
12
|
+
*/
|
|
13
|
+
export declare function renderBudgetTrailer(budget: UpstreamBudget | undefined): string;
|
|
14
|
+
//# sourceMappingURL=render-budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-budget.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/tools/render-budget.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAgBpE;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,GAAG,MAAM,CAI9E"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Markdown rendering for the `budget` enrichment field — shared by the
|
|
3
|
+
* `enrichmentTrailer` of every tool that calls the OpenAlex API. Collapses the three
|
|
4
|
+
* numeric fields to one trailer line; `structuredContent` keeps the numbers.
|
|
5
|
+
* @module mcp-server/tools/render-budget
|
|
6
|
+
*/
|
|
7
|
+
/** OpenAlex prices calls in fractions of a cent — four places keeps them out of exponent form. */
|
|
8
|
+
const USD_DECIMALS = 4;
|
|
9
|
+
function formatUsd(value) {
|
|
10
|
+
return `$${value.toFixed(USD_DECIMALS)}`;
|
|
11
|
+
}
|
|
12
|
+
function formatCountdown(seconds) {
|
|
13
|
+
if (seconds < 60)
|
|
14
|
+
return `${Math.round(seconds)}s`;
|
|
15
|
+
const minutes = Math.round(seconds / 60);
|
|
16
|
+
if (minutes < 60)
|
|
17
|
+
return `${minutes}m`;
|
|
18
|
+
return `${Math.floor(minutes / 60)}h ${minutes % 60}m`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* One-line budget trailer. `costUsd: 0` renders as "free" rather than `$0.0000` because it
|
|
22
|
+
* carries real signal — it means the call was a singleton ID lookup, which OpenAlex does not
|
|
23
|
+
* bill, so batching by ID beats paging a filtered list.
|
|
24
|
+
*/
|
|
25
|
+
export function renderBudgetTrailer(budget) {
|
|
26
|
+
if (!budget)
|
|
27
|
+
return '';
|
|
28
|
+
const cost = budget.costUsd === 0 ? 'free' : formatUsd(budget.costUsd);
|
|
29
|
+
return `**Budget:** ${cost} this call · ${formatUsd(budget.remainingUsd)} left today (resets in ${formatCountdown(budget.resetsInSeconds)})`;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=render-budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-budget.js","sourceRoot":"","sources":["../../../src/mcp-server/tools/render-budget.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,kGAAkG;AAClG,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;AAC3C,CAAC;AAED,SAAS,eAAe,CAAC,OAAe;IACtC,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;IACnD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACzC,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,OAAO,GAAG,CAAC;IACvC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,KAAK,OAAO,GAAG,EAAE,GAAG,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAkC;IACpE,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACvE,OAAO,eAAe,IAAI,gBAAgB,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,0BAA0B,eAAe,CAAC,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC;AAC/I,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Reads OpenAlex's per-request usage accounting off the `X-RateLimit-*`
|
|
3
|
+
* response headers and accumulates it across the requests one tool call issues.
|
|
4
|
+
* @module services/openalex/budget
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* What a tool call spent against the OpenAlex daily budget, and what is left.
|
|
8
|
+
*
|
|
9
|
+
* OpenAlex bills usage in dollars against a daily allowance (roughly $0.10/day anonymous,
|
|
10
|
+
* $1/day with a free account key) that refills at midnight UTC. It reports the accounting
|
|
11
|
+
* on every successful response's headers. `X-RateLimit-Remaining` is a *credit* count
|
|
12
|
+
* against a separate 1000-credit ceiling — proportional to the dollar figure and therefore
|
|
13
|
+
* redundant — so only the `-USD` headers are carried here.
|
|
14
|
+
*/
|
|
15
|
+
export interface UpstreamBudget {
|
|
16
|
+
/** USD this call spent, summed over every upstream request it issued. */
|
|
17
|
+
costUsd: number;
|
|
18
|
+
/** USD left in today's budget as of the most recent upstream response. */
|
|
19
|
+
remainingUsd: number;
|
|
20
|
+
/** Seconds until the daily budget refills. */
|
|
21
|
+
resetsInSeconds: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Extract the budget accounting from a successful OpenAlex response.
|
|
25
|
+
*
|
|
26
|
+
* All three headers are emitted together on every 200 — a list, a group_by, an autocomplete,
|
|
27
|
+
* and a singleton `/works/{id}` lookup alike. Singleton lookups are the reason this reads
|
|
28
|
+
* headers rather than the parsed body: they carry no `meta` wrapper at all, so the body's
|
|
29
|
+
* `cost_usd` (present and identical on the other shapes) is unavailable exactly where
|
|
30
|
+
* `costUsd: 0` is the most interesting signal — fetching by ID is free.
|
|
31
|
+
*
|
|
32
|
+
* Returns `undefined` unless the full set parses. A partial read would report a cost with no
|
|
33
|
+
* budget to weigh it against, which is worse than reporting nothing.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseUpstreamBudget(headers: Headers): UpstreamBudget | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Fold one response's accounting into the running total for a tool call.
|
|
38
|
+
*
|
|
39
|
+
* Cost adds up — a single tool call can issue several billed requests (a sampled search pairs
|
|
40
|
+
* the sample with a population count; the citation-graph walk gates on a seed lookup), and the
|
|
41
|
+
* caller pays for each. The two counters run *down*, so the smaller reading is the more recent
|
|
42
|
+
* one; taking the minimum also stays deterministic when requests are issued in parallel and
|
|
43
|
+
* resolve in arbitrary order.
|
|
44
|
+
*/
|
|
45
|
+
export declare function mergeUpstreamBudget(previous: UpstreamBudget | undefined, next: UpstreamBudget): UpstreamBudget;
|
|
46
|
+
//# sourceMappingURL=budget.d.ts.map
|