@digital-science-dsl/dimensions-analytics-mcp 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/http-main.js +2 -0
  3. package/dist/http-main.js.map +1 -1
  4. package/dist/main.d.ts +0 -5
  5. package/dist/main.d.ts.map +1 -1
  6. package/dist/main.js +3 -1
  7. package/dist/main.js.map +1 -1
  8. package/dist/mcp/auto-update.d.ts +63 -0
  9. package/dist/mcp/auto-update.d.ts.map +1 -0
  10. package/dist/mcp/auto-update.js +295 -0
  11. package/dist/mcp/auto-update.js.map +1 -0
  12. package/dist/mcp/examples/dsl-examples.d.ts +2 -2
  13. package/dist/mcp/examples/dsl-examples.d.ts.map +1 -1
  14. package/dist/mcp/examples/dsl-examples.js +5 -1
  15. package/dist/mcp/examples/dsl-examples.js.map +1 -1
  16. package/dist/mcp/examples/usage-scenarios.d.ts.map +1 -1
  17. package/dist/mcp/examples/usage-scenarios.js +38 -0
  18. package/dist/mcp/examples/usage-scenarios.js.map +1 -1
  19. package/dist/mcp/http-server.js +3 -3
  20. package/dist/mcp/http-server.js.map +1 -1
  21. package/dist/mcp/middleware/field-aliases.d.ts +1 -1
  22. package/dist/mcp/middleware/field-aliases.d.ts.map +1 -1
  23. package/dist/mcp/middleware/field-aliases.js +0 -6
  24. package/dist/mcp/middleware/field-aliases.js.map +1 -1
  25. package/dist/mcp/resources/schema.d.ts +1 -1
  26. package/dist/mcp/resources/schema.d.ts.map +1 -1
  27. package/dist/mcp/resources/schema.js +11 -11
  28. package/dist/mcp/resources/schema.js.map +1 -1
  29. package/dist/mcp/server.d.ts +2 -2
  30. package/dist/mcp/server.d.ts.map +1 -1
  31. package/dist/mcp/server.js +8 -4
  32. package/dist/mcp/server.js.map +1 -1
  33. package/dist/mcp/tools/analytics.d.ts +1 -1
  34. package/dist/mcp/tools/analytics.d.ts.map +1 -1
  35. package/dist/mcp/tools/analytics.js +21 -21
  36. package/dist/mcp/tools/analytics.js.map +1 -1
  37. package/dist/mcp/tools/fetch-search-pages.d.ts +1 -1
  38. package/dist/mcp/tools/fetch-search-pages.d.ts.map +1 -1
  39. package/dist/mcp/tools/fetch-search-pages.js +4 -9
  40. package/dist/mcp/tools/fetch-search-pages.js.map +1 -1
  41. package/dist/mcp/tools/functions.d.ts +1 -1
  42. package/dist/mcp/tools/functions.d.ts.map +1 -1
  43. package/dist/mcp/tools/functions.js +8 -13
  44. package/dist/mcp/tools/functions.js.map +1 -1
  45. package/dist/mcp/tools/lookup.d.ts +1 -1
  46. package/dist/mcp/tools/lookup.d.ts.map +1 -1
  47. package/dist/mcp/tools/lookup.js +12 -17
  48. package/dist/mcp/tools/lookup.js.map +1 -1
  49. package/dist/mcp/tools/query.d.ts +1 -1
  50. package/dist/mcp/tools/query.d.ts.map +1 -1
  51. package/dist/mcp/tools/query.js +4 -9
  52. package/dist/mcp/tools/query.js.map +1 -1
  53. package/dist/mcp/tools/schema.d.ts +1 -1
  54. package/dist/mcp/tools/schema.d.ts.map +1 -1
  55. package/dist/mcp/tools/schema.js +7 -11
  56. package/dist/mcp/tools/schema.js.map +1 -1
  57. package/dist/mcp/tools/search.d.ts +1 -1
  58. package/dist/mcp/tools/search.d.ts.map +1 -1
  59. package/dist/mcp/tools/search.js +4 -9
  60. package/dist/mcp/tools/search.js.map +1 -1
  61. package/dist/mcp/tools/similar-documents.d.ts +26 -0
  62. package/dist/mcp/tools/similar-documents.d.ts.map +1 -0
  63. package/dist/mcp/tools/similar-documents.js +166 -0
  64. package/dist/mcp/tools/similar-documents.js.map +1 -0
  65. package/dist/mcp/usage-tracking.d.ts +1 -1
  66. package/dist/mcp/usage-tracking.d.ts.map +1 -1
  67. package/dist/mcp/utils.d.ts +1 -2
  68. package/dist/mcp/utils.d.ts.map +1 -1
  69. package/dist/mcp/utils.js +0 -4
  70. package/dist/mcp/utils.js.map +1 -1
  71. package/package.json +6 -2
  72. package/src/http-main.ts +3 -0
  73. package/src/main.ts +4 -2
  74. package/src/mcp/auto-update.ts +379 -0
  75. package/src/mcp/examples/dsl-examples.ts +5 -1
  76. package/src/mcp/examples/usage-scenarios.ts +38 -0
  77. package/src/mcp/http-server.ts +3 -3
  78. package/src/mcp/middleware/field-aliases.ts +1 -2
  79. package/src/mcp/resources/schema.ts +12 -12
  80. package/src/mcp/server.ts +9 -5
  81. package/src/mcp/tools/analytics.ts +26 -26
  82. package/src/mcp/tools/fetch-search-pages.ts +5 -6
  83. package/src/mcp/tools/functions.ts +9 -10
  84. package/src/mcp/tools/lookup.ts +13 -14
  85. package/src/mcp/tools/query.ts +5 -6
  86. package/src/mcp/tools/schema.ts +8 -9
  87. package/src/mcp/tools/search.ts +5 -6
  88. package/src/mcp/tools/similar-documents.ts +231 -0
  89. package/src/mcp/usage-tracking.ts +1 -1
  90. package/src/mcp/utils.ts +1 -3
  91. package/test/examples/dsl-examples.test.ts +8 -0
  92. package/test/examples/usage-scenarios.test.ts +5 -3
  93. package/test/integration/harness.ts +1 -3
  94. package/test/integration/hosted-client.ts +1 -3
  95. package/test/integration/hosted.e2e.test.ts +1 -1
  96. package/test/integration/suites/index.ts +2 -0
  97. package/test/integration/suites/similar-documents.integration.ts +51 -0
  98. package/test/integration/verify-usage-scenarios.ts +1 -3
  99. package/test/mcp/auto-update.test.ts +347 -0
  100. package/test/middleware/field-aliases.test.ts +1 -1
  101. package/test/resources/schema.test.ts +5 -1
  102. package/test/server.test.ts +17 -3
  103. package/test/tools/similar-documents.test.ts +173 -0
  104. package/tsconfig.tsbuildinfo +1 -1
@@ -12,11 +12,13 @@ export const DSL_EXAMPLES_BY_ENTITY: Record<string, readonly string[]> = {
12
12
  'search publications for "machine learning" return publications[id+title+doi] limit 10',
13
13
  'search publications for "CRISPR" where year >= 2020 return publications[id+title+times_cited+year] sort by times_cited desc limit 25',
14
14
  'search publications in title_only for "gene therapy" return publications[basics] limit 10',
15
+ 'search publications for similar_documents("After spinal cord injury, macrophages infiltrate the lesion site") where year > 2015 return publications[id+title+year] sort by score desc limit 5',
15
16
  "search publications return publications facet journal limit 20",
16
17
  ],
17
18
  grants: [
18
19
  'search grants for "cancer immunotherapy" return grants limit 10',
19
20
  'search grants where funder_org_name = "National Institutes of Health" return grants[basics] limit 10',
21
+ 'search grants for similar_documents("Cancer immunotherapy with checkpoint inhibitors and CAR-T") where start_year >= 2020 return grants[id+title] sort by score desc limit 5',
20
22
  "search grants return grants aggregate funding_usd",
21
23
  ],
22
24
  patents: [
@@ -53,13 +55,15 @@ export const DSL_EXAMPLES_BY_ENTITY: Record<string, readonly string[]> = {
53
55
  ],
54
56
  };
55
57
 
56
- /** General DSL patterns (describe, functions, pagination). */
58
+ /** General DSL patterns (describe, functions, pagination, similarity). */
57
59
  export const DSL_GENERAL_EXAMPLES = [
58
60
  "describe version",
59
61
  "describe schema",
60
62
  "describe source publications",
61
63
  'extract_concepts(text: "CRISPR gene editing enables precise DNA modification")',
62
64
  'classify(text: "Stem cell therapy for diabetes", system: "for")',
65
+ 'search publications for similar_documents("After spinal cord injury, macrophages infiltrate the lesion site") where year > 2015 return publications[id+title+year] sort by score desc limit 5',
66
+ 'search grants for similar_documents("Cancer immunotherapy with checkpoint inhibitors and CAR-T") where start_year >= 2020 return grants[id+title] sort by score desc limit 5',
63
67
  ] as const;
64
68
 
65
69
  /** Link to official DSL documentation. */
@@ -164,6 +164,44 @@ export const USAGE_SCENARIOS: readonly UsageScenario[] = [
164
164
  },
165
165
  dslContains: ["large language model", "citations_per_year(2018, 2024)"],
166
166
  },
167
+ {
168
+ id: "similar-documents",
169
+ goal: "Similar papers from an abstract",
170
+ prompt: "Find publications similar to this abstract about spinal cord injury macrophages.",
171
+ tool: "similar_documents",
172
+ args: {
173
+ entityType: "publications",
174
+ text: "After spinal cord injury, macrophages infiltrate the lesion site and contribute to both tissue damage and repair processes.",
175
+ yearFrom: 2016,
176
+ limit: 10,
177
+ fields: ["id", "title", "year"],
178
+ },
179
+ dslContains: [
180
+ "search publications for similar_documents(",
181
+ "year >= 2016",
182
+ "sort by score desc",
183
+ "limit 10",
184
+ ],
185
+ },
186
+ {
187
+ id: "similar-grants",
188
+ goal: "Similar grants from a description",
189
+ prompt: "Find grants similar to this description of CAR-T cancer immunotherapy.",
190
+ tool: "similar_documents",
191
+ args: {
192
+ entityType: "grants",
193
+ text: "Development of novel cancer immunotherapy approaches using checkpoint inhibitors and CAR-T cell engineering for solid tumors.",
194
+ yearFrom: 2020,
195
+ limit: 10,
196
+ fields: ["id", "title", "funding_usd", "start_year"],
197
+ },
198
+ dslContains: [
199
+ "search grants for similar_documents(",
200
+ "start_year >= 2020",
201
+ "sort by score desc",
202
+ "limit 10",
203
+ ],
204
+ },
167
205
  {
168
206
  id: "build-complex-query",
169
207
  goal: "Build a complex query",
@@ -5,8 +5,8 @@
5
5
 
6
6
  import { randomUUID } from "node:crypto";
7
7
  import type { IncomingMessage, Server } from "node:http";
8
- import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
9
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
8
+ import { createMcpExpressApp } from "@modelcontextprotocol/express";
9
+ import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
10
10
  import type { Request, Response } from "express";
11
11
  import type { HostedEnvConfig } from "../client/deployment-config.js";
12
12
  import { AuthenticationError } from "../client/errors.js";
@@ -73,7 +73,7 @@ export function startHostedHttpServer(options: HostedHttpServerOptions): HostedH
73
73
  mcpClient,
74
74
  });
75
75
 
76
- const transport = new StreamableHTTPServerTransport({
76
+ const transport = new NodeStreamableHTTPServerTransport({
77
77
  sessionIdGenerator: undefined,
78
78
  });
79
79
 
@@ -4,8 +4,7 @@
4
4
  * via a higher-order function (decorator) pattern.
5
5
  * @module mcp/middleware/field-aliases
6
6
  */
7
-
8
- import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
7
+ import type { CallToolResult } from "@modelcontextprotocol/server";
9
8
  import type { EntityType } from "../../dsl/index.js";
10
9
 
11
10
  // ---------------------------------------------------------------------------
@@ -3,8 +3,8 @@
3
3
  * @module mcp/resources/schema
4
4
  */
5
5
 
6
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
- import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import type { McpServer } from "@modelcontextprotocol/server";
7
+ import { ResourceTemplate } from "@modelcontextprotocol/server";
8
8
  import {
9
9
  buildUsagePolicy,
10
10
  type DescribeField,
@@ -32,7 +32,7 @@ import type { SchemaContext } from "../schema/context.js";
32
32
  */
33
33
  export function registerSchemaResources(server: McpServer, context: SchemaContext): void {
34
34
  const schemaStore = () => context.store;
35
- server.resource(
35
+ server.registerResource(
36
36
  "schema-summary",
37
37
  "dimensions://schema/summary",
38
38
  {
@@ -52,7 +52,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
52
52
  );
53
53
 
54
54
  // Full schema
55
- server.resource(
55
+ server.registerResource(
56
56
  "schema-full",
57
57
  "dimensions://schema",
58
58
  {
@@ -72,7 +72,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
72
72
  );
73
73
 
74
74
  // Version
75
- server.resource(
75
+ server.registerResource(
76
76
  "schema-version",
77
77
  "dimensions://schema/version",
78
78
  {
@@ -95,7 +95,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
95
95
  );
96
96
 
97
97
  // Limits (static until upstream `describe limits` exists)
98
- server.resource(
98
+ server.registerResource(
99
99
  "schema-limits",
100
100
  "dimensions://schema/limits",
101
101
  {
@@ -113,7 +113,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
113
113
  }),
114
114
  );
115
115
 
116
- server.resource(
116
+ server.registerResource(
117
117
  "schema-policy",
118
118
  "dimensions://schema/policy",
119
119
  {
@@ -132,7 +132,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
132
132
  }),
133
133
  );
134
134
 
135
- server.resource(
135
+ server.registerResource(
136
136
  "dsl-examples",
137
137
  "dimensions://examples",
138
138
  {
@@ -160,7 +160,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
160
160
  }),
161
161
  );
162
162
 
163
- server.resource(
163
+ server.registerResource(
164
164
  "dsl-examples-by-source",
165
165
  new ResourceTemplate("dimensions://examples/{source}", {
166
166
  list: async () => ({
@@ -219,7 +219,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
219
219
  );
220
220
 
221
221
  const sourceNames = () => schemaStore().sourceNames();
222
- server.resource(
222
+ server.registerResource(
223
223
  "schema-sources",
224
224
  new ResourceTemplate("dimensions://schema/sources/{sourceName}", {
225
225
  list: async () => ({
@@ -256,7 +256,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
256
256
  );
257
257
 
258
258
  const entityNames = () => schemaStore().entityNames();
259
- server.resource(
259
+ server.registerResource(
260
260
  "schema-entities",
261
261
  new ResourceTemplate("dimensions://schema/entities/{entityName}", {
262
262
  list: async () => ({
@@ -294,7 +294,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
294
294
 
295
295
  // Compatibility view for structured search tools
296
296
  const entityTypes = () => schemaStore().structuredEntityTypes();
297
- server.resource(
297
+ server.registerResource(
298
298
  "fields",
299
299
  new ResourceTemplate("dimensions://fields/{entityType}", {
300
300
  list: async () => ({
package/src/mcp/server.ts CHANGED
@@ -4,9 +4,9 @@
4
4
  * @module mcp/server
5
5
  */
6
6
 
7
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
- import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
7
+ import type { Transport } from "@modelcontextprotocol/server";
8
+ import { McpServer } from "@modelcontextprotocol/server";
9
+ import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
10
10
  import pkg from "../../package.json" with { type: "json" };
11
11
  import { type HostedEnvConfig, loadDeploymentConfig } from "../client/index.js";
12
12
  import { createBootstrapDimensionsClient, createDimensionsClient } from "../dsl/create-client.js";
@@ -27,6 +27,7 @@ import { registerLookupTools } from "./tools/lookup.js";
27
27
  import { registerQueryTools } from "./tools/query.js";
28
28
  import { registerSchemaTools, validateFieldAliases } from "./tools/schema.js";
29
29
  import { registerSearchTools } from "./tools/search.js";
30
+ import { registerSimilarDocumentsTool } from "./tools/similar-documents.js";
30
31
  import { initMcpUsageTracking } from "./usage-tracking.js";
31
32
 
32
33
  /**
@@ -94,7 +95,7 @@ export function buildServerInstructions(schemaStore: SchemaStore): string {
94
95
  [
95
96
  "Workflow:",
96
97
  "(1) Discover schema — read dimensions://schema/summary, dimensions://fields/{entity}, dimensions://examples/{source}, or describe_schema;",
97
- "(2) Search — search_* for keyword discovery with filters; get_by_doi, get_by_pmid, get_by_id for known identifiers;",
98
+ "(2) Search — search_* for keyword discovery with filters; similar_documents for topic-similar pubs/grants from abstract text; get_by_doi, get_by_pmid, get_by_id for known identifiers;",
98
99
  "(3) Analyze — facet_query, aggregate_query, citation_trend, funding_trend;",
99
100
  "(4) Drill down — get_by_id, search_* with filters (e.g. researchers.id, research_orgs.id), facet_query for top researchers at an org.",
100
101
  ].join(" "),
@@ -105,12 +106,14 @@ export function buildServerInstructions(schemaStore: SchemaStore): string {
105
106
  "search_organizations for institution lookup — prefer GRID id in filters when the name is ambiguous;",
106
107
  "search_source_titles for journals / ISSN lookup (not articles); search_reports for technical reports;",
107
108
  "search_funder_groups / search_research_org_groups for curated group name → member GRID ids;",
109
+ "similar_documents finds concept-similar publications/grants from prose (not embeddings); for a known ID, get_by_id then pass abstract/description as text;",
108
110
  "facet_query supports yearFrom/yearTo for year-scoped facets.",
109
111
  ].join(" "),
110
112
  [
111
113
  "Query construction:",
112
114
  "for ranked publication search (e.g. most-cited since 2020), use search_publications with query, yearFrom, sortBy (times_cited or total_citations), limit — do not hand-write execute_dsl;",
113
- "use execute_dsl for boolean concept groups and DSL special functions;",
115
+ "for similar-document lookup, use similar_documents do not hand-write similar_documents() in execute_dsl;",
116
+ "use execute_dsl for boolean concept groups and remaining DSL special functions (classify, extract_concepts);",
114
117
  "use execute_dsl only when structured tools are insufficient; DSL order is return ... sort by FIELD order limit N (never limit before sort).",
115
118
  ].join(" "),
116
119
  [
@@ -138,6 +141,7 @@ function registerAllTools(
138
141
  registerLookupTools(server, client);
139
142
  registerQueryTools(server, client, schemaStore);
140
143
  registerFunctionTools(server, client);
144
+ registerSimilarDocumentsTool(server, client);
141
145
  registerAnalyticsTools(server, client, schemaStore);
142
146
  registerSchemaTools(server, client, schemaContext);
143
147
  }
@@ -4,8 +4,8 @@
4
4
  * @module mcp/tools/analytics
5
5
  */
6
6
 
7
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
- import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
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 McpError(
64
- ErrorCode.InvalidParams,
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 McpError(
81
- ErrorCode.InvalidParams,
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 McpError(
365
- ErrorCode.InvalidParams,
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 McpError(
435
- ErrorCode.InvalidParams,
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) => {
@@ -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(
@@ -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,