@digital-science-dsl/dimensions-analytics-mcp 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/dist/http-main.js +2 -0
- package/dist/http-main.js.map +1 -1
- package/dist/main.d.ts +0 -5
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +3 -1
- package/dist/main.js.map +1 -1
- package/dist/mcp/auto-update.d.ts +63 -0
- package/dist/mcp/auto-update.d.ts.map +1 -0
- package/dist/mcp/auto-update.js +295 -0
- package/dist/mcp/auto-update.js.map +1 -0
- package/dist/mcp/examples/dsl-examples.d.ts +2 -2
- package/dist/mcp/examples/dsl-examples.d.ts.map +1 -1
- package/dist/mcp/examples/dsl-examples.js +3 -1
- package/dist/mcp/examples/dsl-examples.js.map +1 -1
- package/dist/mcp/examples/usage-scenarios.d.ts.map +1 -1
- package/dist/mcp/examples/usage-scenarios.js +19 -0
- package/dist/mcp/examples/usage-scenarios.js.map +1 -1
- package/dist/mcp/http-server.js +3 -3
- package/dist/mcp/http-server.js.map +1 -1
- package/dist/mcp/middleware/field-aliases.d.ts +1 -1
- package/dist/mcp/middleware/field-aliases.d.ts.map +1 -1
- package/dist/mcp/middleware/field-aliases.js +0 -6
- package/dist/mcp/middleware/field-aliases.js.map +1 -1
- package/dist/mcp/resources/schema.d.ts +1 -1
- package/dist/mcp/resources/schema.d.ts.map +1 -1
- package/dist/mcp/resources/schema.js +11 -11
- package/dist/mcp/resources/schema.js.map +1 -1
- package/dist/mcp/server.d.ts +2 -2
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +2 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/analytics.d.ts +1 -1
- package/dist/mcp/tools/analytics.d.ts.map +1 -1
- package/dist/mcp/tools/analytics.js +21 -21
- package/dist/mcp/tools/analytics.js.map +1 -1
- package/dist/mcp/tools/fetch-search-pages.d.ts +1 -1
- package/dist/mcp/tools/fetch-search-pages.d.ts.map +1 -1
- package/dist/mcp/tools/fetch-search-pages.js +4 -9
- package/dist/mcp/tools/fetch-search-pages.js.map +1 -1
- package/dist/mcp/tools/functions.d.ts +1 -1
- package/dist/mcp/tools/functions.d.ts.map +1 -1
- package/dist/mcp/tools/functions.js +8 -13
- package/dist/mcp/tools/functions.js.map +1 -1
- package/dist/mcp/tools/lookup.d.ts +1 -1
- package/dist/mcp/tools/lookup.d.ts.map +1 -1
- package/dist/mcp/tools/lookup.js +12 -17
- package/dist/mcp/tools/lookup.js.map +1 -1
- package/dist/mcp/tools/query.d.ts +1 -1
- package/dist/mcp/tools/query.d.ts.map +1 -1
- package/dist/mcp/tools/query.js +4 -9
- package/dist/mcp/tools/query.js.map +1 -1
- package/dist/mcp/tools/schema.d.ts +1 -1
- package/dist/mcp/tools/schema.d.ts.map +1 -1
- package/dist/mcp/tools/schema.js +7 -11
- package/dist/mcp/tools/schema.js.map +1 -1
- package/dist/mcp/tools/search.d.ts +1 -1
- package/dist/mcp/tools/search.d.ts.map +1 -1
- package/dist/mcp/tools/search.js +4 -9
- package/dist/mcp/tools/search.js.map +1 -1
- package/dist/mcp/tools/similar-documents.d.ts +1 -1
- package/dist/mcp/tools/similar-documents.d.ts.map +1 -1
- package/dist/mcp/tools/similar-documents.js +4 -9
- package/dist/mcp/tools/similar-documents.js.map +1 -1
- package/dist/mcp/usage-tracking.d.ts +1 -1
- package/dist/mcp/usage-tracking.d.ts.map +1 -1
- package/dist/mcp/utils.d.ts +1 -2
- package/dist/mcp/utils.d.ts.map +1 -1
- package/dist/mcp/utils.js +0 -4
- package/dist/mcp/utils.js.map +1 -1
- package/package.json +6 -2
- package/src/http-main.ts +3 -0
- package/src/main.ts +4 -2
- package/src/mcp/auto-update.ts +379 -0
- package/src/mcp/examples/dsl-examples.ts +3 -1
- package/src/mcp/examples/usage-scenarios.ts +19 -0
- package/src/mcp/http-server.ts +3 -3
- package/src/mcp/middleware/field-aliases.ts +1 -2
- package/src/mcp/resources/schema.ts +12 -12
- package/src/mcp/server.ts +3 -3
- package/src/mcp/tools/analytics.ts +26 -26
- package/src/mcp/tools/fetch-search-pages.ts +5 -6
- package/src/mcp/tools/functions.ts +9 -10
- package/src/mcp/tools/lookup.ts +13 -14
- package/src/mcp/tools/query.ts +5 -6
- package/src/mcp/tools/schema.ts +8 -9
- package/src/mcp/tools/search.ts +5 -6
- package/src/mcp/tools/similar-documents.ts +5 -6
- package/src/mcp/usage-tracking.ts +1 -1
- package/src/mcp/utils.ts +1 -3
- package/test/examples/dsl-examples.test.ts +8 -0
- package/test/examples/usage-scenarios.test.ts +3 -3
- package/test/integration/harness.ts +1 -3
- package/test/integration/hosted-client.ts +1 -3
- package/test/integration/hosted.e2e.test.ts +1 -1
- package/test/integration/verify-usage-scenarios.ts +1 -3
- package/test/mcp/auto-update.test.ts +347 -0
- package/test/middleware/field-aliases.test.ts +1 -1
- package/test/resources/schema.test.ts +5 -1
- package/test/server.test.ts +15 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* @module mcp/tools/analytics
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { McpServer } from "@modelcontextprotocol/
|
|
8
|
-
import {
|
|
7
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
8
|
+
import { ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/server";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import type { Currency, DimensionsClient, EntityType } from "../../dsl/index.js";
|
|
11
11
|
import {
|
|
@@ -60,8 +60,8 @@ function entityTypeEnum(schemaStore: SchemaStore) {
|
|
|
60
60
|
function assertFacetField(schemaStore: SchemaStore, entity: string, facetField: string): void {
|
|
61
61
|
const valid = schemaStore.facetFields(entity);
|
|
62
62
|
if (!valid.includes(facetField)) {
|
|
63
|
-
throw new
|
|
64
|
-
|
|
63
|
+
throw new ProtocolError(
|
|
64
|
+
ProtocolErrorCode.InvalidParams,
|
|
65
65
|
`Invalid facet field "${facetField}" for entity "${entity}". Valid fields: ${valid.join(", ") || "(none)"}`,
|
|
66
66
|
);
|
|
67
67
|
}
|
|
@@ -77,8 +77,8 @@ function assertIndicators(schemaStore: SchemaStore, entity: string, indicators:
|
|
|
77
77
|
const valid = new Set(schemaStore.metrics(entity));
|
|
78
78
|
const invalid = indicators.filter((name) => !valid.has(name));
|
|
79
79
|
if (invalid.length > 0) {
|
|
80
|
-
throw new
|
|
81
|
-
|
|
80
|
+
throw new ProtocolError(
|
|
81
|
+
ProtocolErrorCode.InvalidParams,
|
|
82
82
|
`Invalid indicator(s) for entity "${entity}": ${invalid.join(", ")}. Valid metrics: ${[...valid].join(", ") || "(none)"}`,
|
|
83
83
|
);
|
|
84
84
|
}
|
|
@@ -131,7 +131,7 @@ export function registerAnalyticsTools(
|
|
|
131
131
|
description:
|
|
132
132
|
"Distribution analysis across entity dimensions. Returns facet buckets showing how records " +
|
|
133
133
|
"are distributed across a categorical field. Example: 'What journals publish the most CRISPR research?'",
|
|
134
|
-
inputSchema: {
|
|
134
|
+
inputSchema: z.object({
|
|
135
135
|
entityType: entityTypeSchema,
|
|
136
136
|
facetField: z
|
|
137
137
|
.string()
|
|
@@ -165,13 +165,13 @@ export function registerAnalyticsTools(
|
|
|
165
165
|
.array(ExtendedWhereFilterSchema)
|
|
166
166
|
.optional()
|
|
167
167
|
.describe("Additional where-clause filters before faceting"),
|
|
168
|
-
},
|
|
169
|
-
outputSchema: {
|
|
168
|
+
}),
|
|
169
|
+
outputSchema: z.object({
|
|
170
170
|
entityType: z.string().describe("Entity type analyzed"),
|
|
171
171
|
facetField: z.string().describe("Field faceted on"),
|
|
172
172
|
totalBuckets: z.number().describe("Number of facet buckets returned"),
|
|
173
173
|
buckets: z.array(z.record(z.string(), z.unknown())).describe("Facet bucket distribution"),
|
|
174
|
-
},
|
|
174
|
+
}),
|
|
175
175
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
176
176
|
},
|
|
177
177
|
withFieldAliases(
|
|
@@ -221,7 +221,7 @@ export function registerAnalyticsTools(
|
|
|
221
221
|
description:
|
|
222
222
|
"Metric aggregation on facet buckets. Returns facet buckets enriched with aggregated indicator " +
|
|
223
223
|
"values. Example: 'Which funders have the highest average citation ratio?'",
|
|
224
|
-
inputSchema: {
|
|
224
|
+
inputSchema: z.object({
|
|
225
225
|
entityType: entityTypeSchema,
|
|
226
226
|
facetField: z
|
|
227
227
|
.string()
|
|
@@ -263,8 +263,8 @@ export function registerAnalyticsTools(
|
|
|
263
263
|
.array(ExtendedWhereFilterSchema)
|
|
264
264
|
.optional()
|
|
265
265
|
.describe("Additional where-clause filters before aggregating"),
|
|
266
|
-
},
|
|
267
|
-
outputSchema: {
|
|
266
|
+
}),
|
|
267
|
+
outputSchema: z.object({
|
|
268
268
|
entityType: z.string().describe("Entity type analyzed"),
|
|
269
269
|
facetField: z.string().describe("Field faceted on"),
|
|
270
270
|
indicators: z.array(z.string()).describe("Aggregation indicators computed"),
|
|
@@ -272,7 +272,7 @@ export function registerAnalyticsTools(
|
|
|
272
272
|
buckets: z
|
|
273
273
|
.array(z.record(z.string(), z.unknown()))
|
|
274
274
|
.describe("Facet buckets with aggregated values"),
|
|
275
|
-
},
|
|
275
|
+
}),
|
|
276
276
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
277
277
|
},
|
|
278
278
|
withFieldAliases(
|
|
@@ -330,7 +330,7 @@ export function registerAnalyticsTools(
|
|
|
330
330
|
description:
|
|
331
331
|
"Year-by-year citation counts for a topic. Returns annual citation totals for publications " +
|
|
332
332
|
"matching the query over the specified year range.",
|
|
333
|
-
inputSchema: {
|
|
333
|
+
inputSchema: z.object({
|
|
334
334
|
query: z
|
|
335
335
|
.string()
|
|
336
336
|
.min(1)
|
|
@@ -347,22 +347,22 @@ export function registerAnalyticsTools(
|
|
|
347
347
|
.min(1900)
|
|
348
348
|
.max(2100)
|
|
349
349
|
.describe("Last year of the time range (inclusive)"),
|
|
350
|
-
},
|
|
351
|
-
outputSchema: {
|
|
350
|
+
}),
|
|
351
|
+
outputSchema: z.object({
|
|
352
352
|
query: z.string().describe("Search query used"),
|
|
353
353
|
startYear: z.number().describe("First year of range"),
|
|
354
354
|
endYear: z.number().describe("Last year of range"),
|
|
355
355
|
citationsPerYear: z
|
|
356
356
|
.array(z.record(z.string(), z.unknown()))
|
|
357
357
|
.describe("Annual citation totals"),
|
|
358
|
-
},
|
|
358
|
+
}),
|
|
359
359
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
360
360
|
},
|
|
361
361
|
async (args) => {
|
|
362
362
|
try {
|
|
363
363
|
if (args.startYear > args.endYear) {
|
|
364
|
-
throw new
|
|
365
|
-
|
|
364
|
+
throw new ProtocolError(
|
|
365
|
+
ProtocolErrorCode.InvalidParams,
|
|
366
366
|
`startYear (${args.startYear}) must be ≤ endYear (${args.endYear})`,
|
|
367
367
|
);
|
|
368
368
|
}
|
|
@@ -393,7 +393,7 @@ export function registerAnalyticsTools(
|
|
|
393
393
|
description:
|
|
394
394
|
"Year-by-year funding totals for a topic. Returns annual grant funding amounts for grants " +
|
|
395
395
|
"matching the query over the specified year range.",
|
|
396
|
-
inputSchema: {
|
|
396
|
+
inputSchema: z.object({
|
|
397
397
|
query: z
|
|
398
398
|
.string()
|
|
399
399
|
.min(1)
|
|
@@ -416,8 +416,8 @@ export function registerAnalyticsTools(
|
|
|
416
416
|
.describe(
|
|
417
417
|
`Currency for funding amounts (default USD). Supported: ${SUPPORTED_CURRENCIES.join(", ")}.`,
|
|
418
418
|
),
|
|
419
|
-
},
|
|
420
|
-
outputSchema: {
|
|
419
|
+
}),
|
|
420
|
+
outputSchema: z.object({
|
|
421
421
|
query: z.string().describe("Search query used"),
|
|
422
422
|
startYear: z.number().describe("First year of range"),
|
|
423
423
|
endYear: z.number().describe("Last year of range"),
|
|
@@ -425,14 +425,14 @@ export function registerAnalyticsTools(
|
|
|
425
425
|
fundingPerYear: z
|
|
426
426
|
.array(z.record(z.string(), z.unknown()))
|
|
427
427
|
.describe("Annual funding totals"),
|
|
428
|
-
},
|
|
428
|
+
}),
|
|
429
429
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
430
430
|
},
|
|
431
431
|
async (args) => {
|
|
432
432
|
try {
|
|
433
433
|
if (args.startYear > args.endYear) {
|
|
434
|
-
throw new
|
|
435
|
-
|
|
434
|
+
throw new ProtocolError(
|
|
435
|
+
ProtocolErrorCode.InvalidParams,
|
|
436
436
|
`startYear (${args.startYear}) must be ≤ endYear (${args.endYear})`,
|
|
437
437
|
);
|
|
438
438
|
}
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* Supports single-page, aggregate, and JSONL file export modes.
|
|
4
4
|
* @module mcp/tools/fetch-search-pages
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
8
7
|
import { z } from "zod";
|
|
9
8
|
import { ValidationError } from "../../client/index.js";
|
|
10
9
|
import {
|
|
@@ -95,7 +94,7 @@ export function registerFetchSearchPagesTools(
|
|
|
95
94
|
"Default mode returns one page. Use aggregate for ID counts/sums without full rows. " +
|
|
96
95
|
"Use file mode to stream JSONL or CSV locally — never for bulk mirroring of Dimensions data. " +
|
|
97
96
|
`Entities: ${entityList}.`,
|
|
98
|
-
inputSchema: {
|
|
97
|
+
inputSchema: z.object({
|
|
99
98
|
entityType: z
|
|
100
99
|
.enum(STRUCTURED_ENTITY_TYPES)
|
|
101
100
|
.describe("Dimensions source to search (same as search_* tools)"),
|
|
@@ -156,8 +155,8 @@ export function registerFetchSearchPagesTools(
|
|
|
156
155
|
.describe(
|
|
157
156
|
"Required when maxPages > 5 or planned records > 5000. Acknowledges reasonable-use policy.",
|
|
158
157
|
),
|
|
159
|
-
},
|
|
160
|
-
outputSchema: {
|
|
158
|
+
}),
|
|
159
|
+
outputSchema: z.object({
|
|
161
160
|
mode: z.string(),
|
|
162
161
|
entityType: z.string(),
|
|
163
162
|
totalCount: z.number().optional(),
|
|
@@ -176,7 +175,7 @@ export function registerFetchSearchPagesTools(
|
|
|
176
175
|
totalFundingUsd: z.number().optional(),
|
|
177
176
|
})
|
|
178
177
|
.optional(),
|
|
179
|
-
},
|
|
178
|
+
}),
|
|
180
179
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
181
180
|
},
|
|
182
181
|
withFieldAliases(
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* Entity resolution helpers (affiliations, grants).
|
|
4
4
|
* @module mcp/tools/functions
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
8
7
|
import { z } from "zod";
|
|
9
8
|
import type { DimensionsClient } from "../../dsl/index.js";
|
|
10
9
|
import { registerTrackedTool } from "../usage-tracking.js";
|
|
@@ -24,7 +23,7 @@ export function registerFunctionTools(server: McpServer, client: DimensionsClien
|
|
|
24
23
|
"Resolve and disambiguate organization affiliations using Dimensions entity resolution. " +
|
|
25
24
|
"Takes freetext affiliation strings or structured organization data and returns matched " +
|
|
26
25
|
"organizations with GRID/ROR identifiers and confidence scores.",
|
|
27
|
-
inputSchema: {
|
|
26
|
+
inputSchema: z.object({
|
|
28
27
|
affiliations: z
|
|
29
28
|
.array(
|
|
30
29
|
z.object({
|
|
@@ -42,11 +41,11 @@ export function registerFunctionTools(server: McpServer, client: DimensionsClien
|
|
|
42
41
|
.describe(
|
|
43
42
|
"Array of affiliations to resolve. Each must have either 'affiliation' or 'name'",
|
|
44
43
|
),
|
|
45
|
-
},
|
|
46
|
-
outputSchema: {
|
|
44
|
+
}),
|
|
45
|
+
outputSchema: z.object({
|
|
47
46
|
matchCount: z.number().describe("Number of resolved affiliations"),
|
|
48
47
|
affiliations: z.array(z.record(z.string(), z.unknown())).describe("Resolved affiliations"),
|
|
49
|
-
},
|
|
48
|
+
}),
|
|
50
49
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
51
50
|
},
|
|
52
51
|
async (args) => {
|
|
@@ -71,7 +70,7 @@ export function registerFunctionTools(server: McpServer, client: DimensionsClien
|
|
|
71
70
|
"Resolve grant numbers to Dimensions grant records. " +
|
|
72
71
|
"Takes a grant number and optional funder information to find matching grants " +
|
|
73
72
|
"with full metadata including funding amounts, dates, and funder details.",
|
|
74
|
-
inputSchema: {
|
|
73
|
+
inputSchema: z.object({
|
|
75
74
|
grant_number: z.string().min(1).describe("Grant number to look up (e.g., 'R01HL117329')"),
|
|
76
75
|
fundref: z
|
|
77
76
|
.string()
|
|
@@ -81,11 +80,11 @@ export function registerFunctionTools(server: McpServer, client: DimensionsClien
|
|
|
81
80
|
.string()
|
|
82
81
|
.optional()
|
|
83
82
|
.describe("Funder name as alternative to FundRef ID (e.g., 'NIH')"),
|
|
84
|
-
},
|
|
85
|
-
outputSchema: {
|
|
83
|
+
}),
|
|
84
|
+
outputSchema: z.object({
|
|
86
85
|
matchCount: z.number().describe("Number of matched grants"),
|
|
87
86
|
grants: z.array(z.record(z.string(), z.unknown())).describe("Matched grant records"),
|
|
88
|
-
},
|
|
87
|
+
}),
|
|
89
88
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
90
89
|
},
|
|
91
90
|
async (args) => {
|
package/src/mcp/tools/lookup.ts
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* Provides direct lookup by identifiers (DOI, PMID, Dimensions ID).
|
|
4
4
|
* @module mcp/tools/lookup
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
8
7
|
import { z } from "zod";
|
|
9
8
|
import { type DimensionsClient, EntitySchema, type EntityType } from "../../dsl/index.js";
|
|
10
9
|
import { withFieldAliases } from "../middleware/field-aliases.js";
|
|
@@ -29,18 +28,18 @@ export function registerLookupTools(server: McpServer, client: DimensionsClient)
|
|
|
29
28
|
{
|
|
30
29
|
description:
|
|
31
30
|
"Retrieve a publication by its Digital Object Identifier (DOI). Returns full publication details including abstract, citations, and authors.",
|
|
32
|
-
inputSchema: {
|
|
31
|
+
inputSchema: z.object({
|
|
33
32
|
doi: z.string().describe("The DOI to look up (e.g., '10.1038/nature12373')"),
|
|
34
33
|
fields: z
|
|
35
34
|
.array(z.string())
|
|
36
35
|
.optional()
|
|
37
36
|
.describe("Fields to return (e.g., ['id', 'title', 'abstract', 'times_cited'])"),
|
|
38
|
-
},
|
|
39
|
-
outputSchema: {
|
|
37
|
+
}),
|
|
38
|
+
outputSchema: z.object({
|
|
40
39
|
found: z.boolean().describe("Whether the entity was found"),
|
|
41
40
|
publication: z.record(z.string(), z.unknown()).optional().describe("The found entity"),
|
|
42
41
|
message: z.string().optional().describe("Message when not found"),
|
|
43
|
-
},
|
|
42
|
+
}),
|
|
44
43
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
45
44
|
},
|
|
46
45
|
withFieldAliases(
|
|
@@ -83,18 +82,18 @@ export function registerLookupTools(server: McpServer, client: DimensionsClient)
|
|
|
83
82
|
{
|
|
84
83
|
description:
|
|
85
84
|
"Retrieve a publication by its PubMed ID (PMID). Returns full publication details from Dimensions.",
|
|
86
|
-
inputSchema: {
|
|
85
|
+
inputSchema: z.object({
|
|
87
86
|
pmid: z.string().describe("The PubMed ID to look up (e.g., '23846567')"),
|
|
88
87
|
fields: z
|
|
89
88
|
.array(z.string())
|
|
90
89
|
.optional()
|
|
91
90
|
.describe("Fields to return (e.g., ['id', 'title', 'abstract', 'times_cited'])"),
|
|
92
|
-
},
|
|
93
|
-
outputSchema: {
|
|
91
|
+
}),
|
|
92
|
+
outputSchema: z.object({
|
|
94
93
|
found: z.boolean().describe("Whether the entity was found"),
|
|
95
94
|
publication: z.record(z.string(), z.unknown()).optional().describe("The found entity"),
|
|
96
95
|
message: z.string().optional().describe("Message when not found"),
|
|
97
|
-
},
|
|
96
|
+
}),
|
|
98
97
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
99
98
|
},
|
|
100
99
|
withFieldAliases(
|
|
@@ -137,17 +136,17 @@ export function registerLookupTools(server: McpServer, client: DimensionsClient)
|
|
|
137
136
|
{
|
|
138
137
|
description:
|
|
139
138
|
"Retrieve any entity by its Dimensions ID. Supports publications, grants, patents, clinical trials, datasets, policy documents, researchers, organizations, reports, source titles, funder groups, and research org groups.",
|
|
140
|
-
inputSchema: {
|
|
139
|
+
inputSchema: z.object({
|
|
141
140
|
entityType: EntitySchema.describe("The type of entity to look up"),
|
|
142
141
|
id: z.string().describe("The Dimensions ID to look up"),
|
|
143
142
|
fields: z.array(z.string()).optional().describe("Fields to return"),
|
|
144
|
-
},
|
|
145
|
-
outputSchema: {
|
|
143
|
+
}),
|
|
144
|
+
outputSchema: z.object({
|
|
146
145
|
found: z.boolean().describe("Whether the entity was found"),
|
|
147
146
|
entity: z.record(z.string(), z.unknown()).optional().describe("The found entity"),
|
|
148
147
|
entityType: z.string().optional().describe("The type of the found entity"),
|
|
149
148
|
message: z.string().optional().describe("Message when not found"),
|
|
150
|
-
},
|
|
149
|
+
}),
|
|
151
150
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
152
151
|
},
|
|
153
152
|
withFieldAliases(
|
package/src/mcp/tools/query.ts
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* Provides raw DSL query execution for power users.
|
|
4
4
|
* @module mcp/tools/query
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
8
7
|
import { z } from "zod";
|
|
9
8
|
import {
|
|
10
9
|
buildExecuteDslPolicyHints,
|
|
@@ -47,7 +46,7 @@ Examples:
|
|
|
47
46
|
- return funders aggregate funding_usd
|
|
48
47
|
|
|
49
48
|
Structured entity types: ${sources}`,
|
|
50
|
-
inputSchema: {
|
|
49
|
+
inputSchema: z.object({
|
|
51
50
|
dsl: z.string().describe("Complete DSL query string to execute"),
|
|
52
51
|
confirmLargeFetch: z
|
|
53
52
|
.boolean()
|
|
@@ -56,8 +55,8 @@ Structured entity types: ${sources}`,
|
|
|
56
55
|
.describe(
|
|
57
56
|
"Required for deep pagination in DSL (skip≥5000). See dimensions://schema/policy.",
|
|
58
57
|
),
|
|
59
|
-
},
|
|
60
|
-
outputSchema: {
|
|
58
|
+
}),
|
|
59
|
+
outputSchema: z.object({
|
|
61
60
|
result: z.record(z.string(), z.unknown()).describe("Raw DSL query response"),
|
|
62
61
|
policyNotice: z.string().optional().describe("Reasonable-use reminder"),
|
|
63
62
|
largeResultWarning: z
|
|
@@ -68,7 +67,7 @@ Structured entity types: ${sources}`,
|
|
|
68
67
|
.string()
|
|
69
68
|
.optional()
|
|
70
69
|
.describe("Pagination guidance when results are truncated"),
|
|
71
|
-
},
|
|
70
|
+
}),
|
|
72
71
|
annotations: {
|
|
73
72
|
readOnlyHint: true,
|
|
74
73
|
destructiveHint: false,
|
package/src/mcp/tools/schema.ts
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* Schema introspection and validation tools for the MCP server.
|
|
3
3
|
* @module mcp/tools/schema
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
7
6
|
import { z } from "zod";
|
|
8
7
|
import type { DimensionsClient } from "../../dsl/index.js";
|
|
9
8
|
import { buildReverseAliasMap } from "../middleware/field-aliases.js";
|
|
@@ -32,7 +31,7 @@ export function registerSchemaTools(
|
|
|
32
31
|
"Default: compact summary with source/entity counts and resource URIs. " +
|
|
33
32
|
"Use full=true or dimensions://schema for the complete describe payload. " +
|
|
34
33
|
"Optionally fetch live describe for a single source or entity.",
|
|
35
|
-
inputSchema: {
|
|
34
|
+
inputSchema: z.object({
|
|
36
35
|
source: z.string().optional().describe("Source name (e.g. publications)"),
|
|
37
36
|
entity: z.string().optional().describe("Auxiliary entity name (e.g. journals)"),
|
|
38
37
|
full: z
|
|
@@ -43,8 +42,8 @@ export function registerSchemaTools(
|
|
|
43
42
|
.boolean()
|
|
44
43
|
.optional()
|
|
45
44
|
.describe("When true, fetch fresh describe for source/entity from the API"),
|
|
46
|
-
},
|
|
47
|
-
outputSchema: {
|
|
45
|
+
}),
|
|
46
|
+
outputSchema: z.object({
|
|
48
47
|
summary: z
|
|
49
48
|
.record(z.string(), z.unknown())
|
|
50
49
|
.optional()
|
|
@@ -55,7 +54,7 @@ export function registerSchemaTools(
|
|
|
55
54
|
.describe("Full or sliced schema when full=true or source/entity is set"),
|
|
56
55
|
version: z.string().optional().describe("DSL version"),
|
|
57
56
|
stats: z.record(z.string(), z.unknown()).optional().describe("Load stats and provenance"),
|
|
58
|
-
},
|
|
57
|
+
}),
|
|
59
58
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
60
59
|
},
|
|
61
60
|
async (args) => {
|
|
@@ -106,11 +105,11 @@ export function registerSchemaTools(
|
|
|
106
105
|
{
|
|
107
106
|
description:
|
|
108
107
|
"Re-fetch describe schema from the Dimensions API, rebuild in-memory schema, and re-register MCP resources.",
|
|
109
|
-
inputSchema: {},
|
|
110
|
-
outputSchema: {
|
|
108
|
+
inputSchema: z.object({}),
|
|
109
|
+
outputSchema: z.object({
|
|
111
110
|
refreshed: z.boolean(),
|
|
112
111
|
stats: z.record(z.string(), z.unknown()),
|
|
113
|
-
},
|
|
112
|
+
}),
|
|
114
113
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
115
114
|
},
|
|
116
115
|
async () => {
|
package/src/mcp/tools/search.ts
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* Registers `search_{source}` tools from describe schema and shared QueryBuilder handlers.
|
|
4
4
|
* @module mcp/tools/search
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
8
7
|
import { z } from "zod";
|
|
9
8
|
import type { DimensionsClient } from "../../dsl/index.js";
|
|
10
9
|
import {
|
|
@@ -105,11 +104,11 @@ export function registerSearchTools(
|
|
|
105
104
|
toolName,
|
|
106
105
|
{
|
|
107
106
|
description: meta.description,
|
|
108
|
-
inputSchema: {
|
|
107
|
+
inputSchema: z.object({
|
|
109
108
|
...SHARED_SEARCH_INPUT,
|
|
110
109
|
...meta.extraInputSchema,
|
|
111
|
-
},
|
|
112
|
-
outputSchema: {
|
|
110
|
+
}),
|
|
111
|
+
outputSchema: z.object({
|
|
113
112
|
totalCount: z.number().describe("Total matching records"),
|
|
114
113
|
returnedCount: z.number().describe("Records returned in this response"),
|
|
115
114
|
truncated: z
|
|
@@ -119,7 +118,7 @@ export function registerSearchTools(
|
|
|
119
118
|
truncationWarning: z.string().optional().describe("Warning message when truncated"),
|
|
120
119
|
...PAGINATION_OUTPUT_SCHEMA,
|
|
121
120
|
[resultKey]: z.array(z.record(z.string(), z.unknown())).describe("Result records"),
|
|
122
|
-
},
|
|
121
|
+
}),
|
|
123
122
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
124
123
|
},
|
|
125
124
|
withFieldAliases(
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* Uses concept extraction + weighted concepts search (not vector embeddings).
|
|
4
4
|
* @module mcp/tools/similar-documents
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
8
7
|
import { z } from "zod";
|
|
9
8
|
import type { DimensionsClient } from "../../dsl/index.js";
|
|
10
9
|
import {
|
|
@@ -106,7 +105,7 @@ export function registerSimilarDocumentsTool(server: McpServer, client: Dimensio
|
|
|
106
105
|
"Uses Dimensions concept extraction and weighted concepts search (not vector embeddings). " +
|
|
107
106
|
"Prefer this over execute_dsl for similarity. For a known record: get_by_id / get_by_doi first, " +
|
|
108
107
|
"then pass its abstract/description as text. Supported entityType: publications, grants only.",
|
|
109
|
-
inputSchema: {
|
|
108
|
+
inputSchema: z.object({
|
|
110
109
|
entityType: z
|
|
111
110
|
.enum(SIMILAR_DOCUMENTS_ENTITY_TYPES)
|
|
112
111
|
.describe("Entity type to search (publications or grants)"),
|
|
@@ -158,8 +157,8 @@ export function registerSimilarDocumentsTool(server: McpServer, client: Dimensio
|
|
|
158
157
|
.describe(
|
|
159
158
|
"Required when skip≥5000, page≥5, or limit=1000 with skip>0. See dimensions://schema/policy.",
|
|
160
159
|
),
|
|
161
|
-
},
|
|
162
|
-
outputSchema: {
|
|
160
|
+
}),
|
|
161
|
+
outputSchema: z.object({
|
|
163
162
|
entityType: z.enum(SIMILAR_DOCUMENTS_ENTITY_TYPES).describe("Entity type searched"),
|
|
164
163
|
totalCount: z.number().describe("Total matching records"),
|
|
165
164
|
returnedCount: z.number().describe("Records returned in this response"),
|
|
@@ -174,7 +173,7 @@ export function registerSimilarDocumentsTool(server: McpServer, client: Dimensio
|
|
|
174
173
|
.array(z.record(z.string(), z.unknown()))
|
|
175
174
|
.optional()
|
|
176
175
|
.describe("Matching grants (when entityType is grants)"),
|
|
177
|
-
},
|
|
176
|
+
}),
|
|
178
177
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
179
178
|
},
|
|
180
179
|
withFieldAliases(
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
|
-
import type { McpServer } from "@modelcontextprotocol/
|
|
7
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
8
8
|
import pkg from "../../package.json" with { type: "json" };
|
|
9
9
|
import {
|
|
10
10
|
type McpDeploymentMode,
|
package/src/mcp/utils.ts
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
* Utility functions for MCP tools.
|
|
3
3
|
* @module mcp/utils
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
-
import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import type { McpServer, ToolAnnotations } from "@modelcontextprotocol/server";
|
|
8
6
|
import {
|
|
9
7
|
buildPaginationMetadata,
|
|
10
8
|
DimensionsError,
|
|
@@ -20,6 +20,14 @@ describe("dsl-examples", () => {
|
|
|
20
20
|
expect(getDslExamplesForSource("unknown")).toBeUndefined();
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
+
it("includes similar_documents examples for publications and grants", () => {
|
|
24
|
+
const pubs = getDslExamplesForSource("publications") ?? [];
|
|
25
|
+
const grants = getDslExamplesForSource("grants") ?? [];
|
|
26
|
+
expect(pubs.some((q) => q.includes("similar_documents("))).toBe(true);
|
|
27
|
+
expect(grants.some((q) => q.includes("similar_documents("))).toBe(true);
|
|
28
|
+
expect(DSL_GENERAL_EXAMPLES.some((q) => q.includes("similar_documents("))).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
23
31
|
it("uses sort by (not bare sort) and places sort before limit", () => {
|
|
24
32
|
for (const dsl of ALL_EXAMPLES) {
|
|
25
33
|
expect(dsl).not.toMatch(/\blimit \d+ sort\b/);
|
|
@@ -38,9 +38,9 @@ describe("usage-scenarios", () => {
|
|
|
38
38
|
registerSimilarDocumentsTool(server as never, client as never);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
it("documents
|
|
42
|
-
expect(USAGE_SCENARIOS).toHaveLength(
|
|
43
|
-
expect(new Set(USAGE_SCENARIOS.map((s) => s.id)).size).toBe(
|
|
41
|
+
it("documents thirteen natural-language workflows", () => {
|
|
42
|
+
expect(USAGE_SCENARIOS).toHaveLength(13);
|
|
43
|
+
expect(new Set(USAGE_SCENARIOS.map((s) => s.id)).size).toBe(13);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
for (const scenario of USAGE_SCENARIOS) {
|
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
* via in-memory transport, and executes eval suites against it.
|
|
6
6
|
* @module test/integration/harness
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
10
|
-
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
8
|
+
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
11
9
|
import { createMcpServerAsync, type McpServerConfig } from "../../src/mcp/server.js";
|
|
12
10
|
import { missingEnvVars } from "./env.js";
|
|
13
11
|
import type {
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
* MCP client helpers for hosted Streamable HTTP transport.
|
|
3
3
|
* @module test/integration/hosted-client
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
5
|
+
import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
8
6
|
import { hostedMcpEndpoint } from "./env.js";
|
|
9
7
|
|
|
10
8
|
export interface HostedMcpSession {
|
|
@@ -31,7 +31,7 @@ const MOCK_HOSTED: HostedEnvConfig = {
|
|
|
31
31
|
|
|
32
32
|
const TEST_API_KEY = "test-hosted-api-key";
|
|
33
33
|
|
|
34
|
-
describe("Hosted MCP e2e (mocked backends)", () => {
|
|
34
|
+
describe("Hosted MCP e2e (mocked backends)", { timeout: 30_000 }, () => {
|
|
35
35
|
let restoreFetch: (() => void) | undefined;
|
|
36
36
|
let server: HostedHttpServerHandle | undefined;
|
|
37
37
|
let baseUrl: string;
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
* Live verification of docs/USAGE.md natural-language scenarios.
|
|
4
4
|
* Run: node --import tsx apps/mcp/test/integration/verify-usage-scenarios.ts
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8
|
-
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
6
|
+
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
9
7
|
import { USAGE_SCENARIOS } from "../../src/mcp/examples/usage-scenarios.js";
|
|
10
8
|
import { buildServerInstructions, createMcpServerAsync } from "../../src/mcp/server.js";
|
|
11
9
|
|