@digital-science-dsl/dimensions-analytics-mcp 1.3.1 → 1.4.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 (67) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/dist/mcp/batch-fetch.d.ts.map +1 -1
  3. package/dist/mcp/batch-fetch.js +2 -1
  4. package/dist/mcp/batch-fetch.js.map +1 -1
  5. package/dist/mcp/identity-fields.d.ts +22 -0
  6. package/dist/mcp/identity-fields.d.ts.map +1 -0
  7. package/dist/mcp/identity-fields.js +62 -0
  8. package/dist/mcp/identity-fields.js.map +1 -0
  9. package/dist/mcp/profile-urls.d.ts +93 -0
  10. package/dist/mcp/profile-urls.d.ts.map +1 -0
  11. package/dist/mcp/profile-urls.js +141 -0
  12. package/dist/mcp/profile-urls.js.map +1 -0
  13. package/dist/mcp/resources/schema.d.ts.map +1 -1
  14. package/dist/mcp/resources/schema.js +13 -0
  15. package/dist/mcp/resources/schema.js.map +1 -1
  16. package/dist/mcp/server.d.ts.map +1 -1
  17. package/dist/mcp/server.js +5 -1
  18. package/dist/mcp/server.js.map +1 -1
  19. package/dist/mcp/tools/analytics.d.ts.map +1 -1
  20. package/dist/mcp/tools/analytics.js +7 -4
  21. package/dist/mcp/tools/analytics.js.map +1 -1
  22. package/dist/mcp/tools/fetch-search-pages.d.ts.map +1 -1
  23. package/dist/mcp/tools/fetch-search-pages.js +2 -1
  24. package/dist/mcp/tools/fetch-search-pages.js.map +1 -1
  25. package/dist/mcp/tools/lookup.d.ts.map +1 -1
  26. package/dist/mcp/tools/lookup.js +14 -12
  27. package/dist/mcp/tools/lookup.js.map +1 -1
  28. package/dist/mcp/tools/profile-url.d.ts +11 -0
  29. package/dist/mcp/tools/profile-url.d.ts.map +1 -0
  30. package/dist/mcp/tools/profile-url.js +56 -0
  31. package/dist/mcp/tools/profile-url.js.map +1 -0
  32. package/dist/mcp/tools/search-entity-metadata.d.ts.map +1 -1
  33. package/dist/mcp/tools/search-entity-metadata.js +4 -2
  34. package/dist/mcp/tools/search-entity-metadata.js.map +1 -1
  35. package/dist/mcp/tools/search-input.js +1 -1
  36. package/dist/mcp/tools/search-input.js.map +1 -1
  37. package/dist/mcp/tools/search.d.ts.map +1 -1
  38. package/dist/mcp/tools/search.js +4 -5
  39. package/dist/mcp/tools/search.js.map +1 -1
  40. package/dist/mcp/tools/similar-documents.d.ts.map +1 -1
  41. package/dist/mcp/tools/similar-documents.js +4 -5
  42. package/dist/mcp/tools/similar-documents.js.map +1 -1
  43. package/package.json +2 -2
  44. package/src/mcp/batch-fetch.ts +2 -1
  45. package/src/mcp/identity-fields.ts +65 -0
  46. package/src/mcp/profile-urls.ts +180 -0
  47. package/src/mcp/resources/schema.ts +20 -0
  48. package/src/mcp/server.ts +5 -1
  49. package/src/mcp/tools/analytics.ts +7 -4
  50. package/src/mcp/tools/fetch-search-pages.ts +2 -1
  51. package/src/mcp/tools/lookup.ts +14 -9
  52. package/src/mcp/tools/profile-url.ts +70 -0
  53. package/src/mcp/tools/search-entity-metadata.ts +4 -2
  54. package/src/mcp/tools/search-input.ts +1 -1
  55. package/src/mcp/tools/search.ts +4 -5
  56. package/src/mcp/tools/similar-documents.ts +4 -5
  57. package/test/identity-fields.test.ts +44 -0
  58. package/test/profile-urls.test.ts +151 -0
  59. package/test/resources/schema.test.ts +1 -0
  60. package/test/server.test.ts +5 -0
  61. package/test/tools/analytics.test.ts +31 -1
  62. package/test/tools/fetch-search-pages.test.ts +10 -4
  63. package/test/tools/lookup.test.ts +14 -5
  64. package/test/tools/profile-url.test.ts +44 -0
  65. package/test/tools/search.test.ts +51 -3
  66. package/test/tools/similar-documents.test.ts +20 -3
  67. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Tests for canonical Dimensions web profile URL construction (WEBAPPDEV-13680).
3
+ * @module test/profile-urls
4
+ */
5
+
6
+ import { describe, expect, it } from "vitest";
7
+ import {
8
+ attachFacetProfileUrls,
9
+ attachProfileUrl,
10
+ attachProfileUrls,
11
+ buildProfileUrl,
12
+ isProfileEntityType,
13
+ PROFILE_ENTITY_TYPES,
14
+ profileEntityForFacetField,
15
+ profileUrlCatalog,
16
+ } from "../src/mcp/profile-urls.js";
17
+
18
+ describe("buildProfileUrl", () => {
19
+ it("builds the canonical researcher profile path, not /discover/researcher/", () => {
20
+ expect(buildProfileUrl("researchers", "ur.01222634304.39")).toBe(
21
+ "https://app.dimensions.ai/details/entities/publication/author/ur.01222634304.39",
22
+ );
23
+ expect(buildProfileUrl("researchers", "ur.01222634304.39")).not.toContain(
24
+ "/discover/researcher/",
25
+ );
26
+ });
27
+
28
+ it("builds the canonical organization profile path", () => {
29
+ expect(buildProfileUrl("organizations", "grid.168010.e")).toBe(
30
+ "https://app.dimensions.ai/details/organization/grid.168010.e",
31
+ );
32
+ });
33
+
34
+ it("builds document profile paths verified from the DSL dimensions_url field", () => {
35
+ expect(buildProfileUrl("publications", "pub.1015581115")).toBe(
36
+ "https://app.dimensions.ai/details/publication/pub.1015581115",
37
+ );
38
+ expect(buildProfileUrl("grants", "grant.2438800")).toBe(
39
+ "https://app.dimensions.ai/details/grant/grant.2438800",
40
+ );
41
+ expect(buildProfileUrl("patents", "ZW-9994-A1")).toBe(
42
+ "https://app.dimensions.ai/details/patent/ZW-9994-A1",
43
+ );
44
+ expect(buildProfileUrl("clinical_trials", "chictr-trc-14005205")).toBe(
45
+ "https://app.dimensions.ai/details/clinical_trial/chictr-trc-14005205",
46
+ );
47
+ expect(buildProfileUrl("datasets", "dataset.99999999")).toBe(
48
+ "https://app.dimensions.ai/details/data_set/dataset.99999999",
49
+ );
50
+ expect(buildProfileUrl("policy_documents", "policy.9999")).toBe(
51
+ "https://app.dimensions.ai/details/policy_documents/policy.9999",
52
+ );
53
+ });
54
+
55
+ it("uses the configured instance host", () => {
56
+ expect(
57
+ buildProfileUrl("researchers", "ur.01222634304.39", "https://your-instance.dimensions.ai/"),
58
+ ).toBe(
59
+ "https://your-instance.dimensions.ai/details/entities/publication/author/ur.01222634304.39",
60
+ );
61
+ });
62
+
63
+ it("returns undefined for entity types without a canonical profile page", () => {
64
+ expect(buildProfileUrl("reports", "rep.1")).toBeUndefined();
65
+ expect(buildProfileUrl("source_titles", "jour.1016355")).toBeUndefined();
66
+ expect(buildProfileUrl("funder_groups", "fg.1")).toBeUndefined();
67
+ expect(buildProfileUrl("research_org_groups", "rog.1")).toBeUndefined();
68
+ });
69
+
70
+ it("returns undefined for empty ids", () => {
71
+ expect(buildProfileUrl("researchers", " ")).toBeUndefined();
72
+ expect(buildProfileUrl("researchers", "")).toBeUndefined();
73
+ });
74
+ });
75
+
76
+ describe("isProfileEntityType", () => {
77
+ it("lists the entity types that have profile pages", () => {
78
+ expect(PROFILE_ENTITY_TYPES).toContain("researchers");
79
+ expect(PROFILE_ENTITY_TYPES).toContain("organizations");
80
+ expect(isProfileEntityType("researchers")).toBe(true);
81
+ expect(isProfileEntityType("reports")).toBe(false);
82
+ });
83
+ });
84
+
85
+ describe("attachProfileUrl", () => {
86
+ it("adds profile_url from the record id", () => {
87
+ expect(
88
+ attachProfileUrl("researchers", { id: "ur.01222634304.39", last_name: "Halpern" }),
89
+ ).toEqual({
90
+ id: "ur.01222634304.39",
91
+ last_name: "Halpern",
92
+ profile_url:
93
+ "https://app.dimensions.ai/details/entities/publication/author/ur.01222634304.39",
94
+ });
95
+ });
96
+
97
+ it("leaves records without a usable id unchanged", () => {
98
+ const row = { last_name: "Unknown" };
99
+ expect(attachProfileUrl("researchers", row)).toEqual(row);
100
+ });
101
+
102
+ it("does not invent a URL for unsupported entity types", () => {
103
+ const row = { id: "rep.1", title: "Report" };
104
+ expect(attachProfileUrl("reports", row)).toEqual(row);
105
+ });
106
+ });
107
+
108
+ describe("attachProfileUrls", () => {
109
+ it("enriches each row", () => {
110
+ const rows = attachProfileUrls("organizations", [{ id: "grid.168010.e", name: "Stanford" }]);
111
+ expect(rows[0]?.profile_url).toBe(
112
+ "https://app.dimensions.ai/details/organization/grid.168010.e",
113
+ );
114
+ });
115
+ });
116
+
117
+ describe("profileEntityForFacetField", () => {
118
+ it("maps researcher and org facet fields to profile entity types", () => {
119
+ expect(profileEntityForFacetField("researchers")).toBe("researchers");
120
+ expect(profileEntityForFacetField("research_orgs")).toBe("organizations");
121
+ expect(profileEntityForFacetField("funder_orgs")).toBe("organizations");
122
+ expect(profileEntityForFacetField("journal")).toBeUndefined();
123
+ });
124
+ });
125
+
126
+ describe("attachFacetProfileUrls", () => {
127
+ it("adds profile_url on researcher facet buckets", () => {
128
+ const buckets = attachFacetProfileUrls("researchers", [{ id: "ur.01222634304.39", count: 12 }]);
129
+ expect(buckets[0]?.profile_url).toBe(
130
+ "https://app.dimensions.ai/details/entities/publication/author/ur.01222634304.39",
131
+ );
132
+ });
133
+
134
+ it("leaves journal facet buckets unchanged", () => {
135
+ const buckets = [{ id: "jour.1", name: "Nature", count: 42 }];
136
+ expect(attachFacetProfileUrls("journal", buckets)).toEqual(buckets);
137
+ });
138
+ });
139
+
140
+ describe("profileUrlCatalog", () => {
141
+ it("documents the researcher and organization templates", () => {
142
+ const catalog = profileUrlCatalog();
143
+ expect(catalog.warning).toContain("/details/entities/publication/author/{id}");
144
+ expect(catalog.templates.find((t) => t.entityType === "researchers")?.path).toBe(
145
+ "/details/entities/publication/author/{id}",
146
+ );
147
+ expect(catalog.templates.find((t) => t.entityType === "organizations")?.path).toBe(
148
+ "/details/organization/{id}",
149
+ );
150
+ });
151
+ });
@@ -25,6 +25,7 @@ describe("registerSchemaResources", () => {
25
25
  expect(resources.some((r) => r.name === "schema-full")).toBe(true);
26
26
  expect(resources.some((r) => r.name === "schema-limits")).toBe(true);
27
27
  expect(resources.some((r) => r.name === "schema-policy")).toBe(true);
28
+ expect(resources.some((r) => r.name === "schema-profile-urls")).toBe(true);
28
29
  expect(resources.some((r) => r.name === "dsl-examples")).toBe(true);
29
30
  expect(resources.some((r) => r.name === "dsl-examples-by-source")).toBe(true);
30
31
  expect(resources.some((r) => r.name === "fields")).toBe(true);
@@ -64,6 +64,10 @@ describe("createMcpServerAsync", () => {
64
64
  expect(instructions).toContain("similar_documents");
65
65
  expect(instructions).toContain("fetch_search_pages");
66
66
  expect(instructions).toContain("dimensions://schema/policy");
67
+ expect(instructions).toContain("construct_profile_url");
68
+ expect(instructions).toContain("/details/entities/publication/author/");
69
+ expect(instructions).toContain("dimensions://schema/profile-urls");
70
+ expect(instructions).toContain("doi for publications");
67
71
  });
68
72
  });
69
73
 
@@ -137,6 +141,7 @@ describe("MCP protocol", () => {
137
141
  expect(toolNames).toContain("search_publications");
138
142
  expect(toolNames).toContain("fetch_search_pages");
139
143
  expect(toolNames).toContain("similar_documents");
144
+ expect(toolNames).toContain("construct_profile_url");
140
145
  expect(toolNames).toContain("execute_dsl");
141
146
  expect(toolNames).not.toContain("validate_dsl");
142
147
 
@@ -54,6 +54,29 @@ describe("analytics tools", () => {
54
54
  expect(dsl).toContain("journal");
55
55
  });
56
56
 
57
+ it("attaches profile_url on researcher facet buckets", async () => {
58
+ client.rawQuery.mockResolvedValue({
59
+ researchers: [{ id: "ur.01222634304.39", count: 12 }],
60
+ });
61
+
62
+ const result = await callTool(handlers, "facet_query", {
63
+ entityType: "publications",
64
+ facetField: "researchers",
65
+ query: "marine ecology",
66
+ limit: 5,
67
+ });
68
+ const parsed = parseToolResult(result);
69
+
70
+ expect(parsed.buckets).toEqual([
71
+ {
72
+ id: "ur.01222634304.39",
73
+ count: 12,
74
+ profile_url:
75
+ "https://app.dimensions.ai/details/entities/publication/author/ur.01222634304.39",
76
+ },
77
+ ]);
78
+ });
79
+
57
80
  it("formats buckets result", async () => {
58
81
  const mockBuckets = [
59
82
  { id: "1", name: "Nature", count: 42 },
@@ -171,7 +194,14 @@ describe("analytics tools", () => {
171
194
  expect(parsed.facetField).toBe("funder_orgs");
172
195
  expect(parsed.indicators).toEqual(["count"]);
173
196
  expect(parsed.totalBuckets).toBe(1);
174
- expect(parsed.buckets).toEqual(mockBuckets);
197
+ expect(parsed.buckets).toEqual([
198
+ {
199
+ id: "1",
200
+ name: "NIH",
201
+ count: 100,
202
+ profile_url: "https://app.dimensions.ai/details/organization/1",
203
+ },
204
+ ]);
175
205
  });
176
206
 
177
207
  it("resolves indicator aliases", async () => {
@@ -131,7 +131,11 @@ describe("fetch_search_pages", () => {
131
131
 
132
132
  const lines = (await readFile(outputPath, "utf8")).trim().split("\n");
133
133
  expect(lines).toHaveLength(1);
134
- expect(JSON.parse(lines[0])).toEqual({ id: "pub.1", title: "A" });
134
+ expect(JSON.parse(lines[0])).toEqual({
135
+ id: "pub.1",
136
+ title: "A",
137
+ profile_url: "https://app.dimensions.ai/details/publication/pub.1",
138
+ });
135
139
 
136
140
  const meta = JSON.parse(await readFile(`${outputPath}.meta.json`, "utf8"));
137
141
  expect(meta.recordsWritten).toBe(1);
@@ -165,12 +169,14 @@ describe("fetch_search_pages", () => {
165
169
 
166
170
  expect(parsed.outputFormat).toBe("csv");
167
171
  const csv = await readFile(outputPath, "utf8");
168
- expect(csv).toContain("title,id,year\n");
169
- expect(csv).toContain("Alpha,pub.1,2024\n");
172
+ expect(csv).toContain("title,id,profile_url,year\n");
173
+ expect(csv).toContain(
174
+ "Alpha,pub.1,https://app.dimensions.ai/details/publication/pub.1,2024\n",
175
+ );
170
176
 
171
177
  const meta = JSON.parse(await readFile(`${outputPath}.meta.json`, "utf8"));
172
178
  expect(meta.format).toBe("csv");
173
- expect(meta.columns).toEqual(["title", "id", "year"]);
179
+ expect(meta.columns).toEqual(["title", "id", "profile_url", "year"]);
174
180
  });
175
181
 
176
182
  it("requires outputPath", async () => {
@@ -57,7 +57,7 @@ describe("lookup tools", () => {
57
57
  });
58
58
 
59
59
  const dsl = client.rawQuery.mock.calls[0][0] as string;
60
- expect(dsl).toContain("publications[id+title]");
60
+ expect(dsl).toContain("publications[id+doi+title]");
61
61
  });
62
62
 
63
63
  it("returns found publication when match exists", async () => {
@@ -68,7 +68,10 @@ describe("lookup tools", () => {
68
68
  const data = parseToolResult(result);
69
69
 
70
70
  expect(data.found).toBe(true);
71
- expect(data.publication).toEqual(pub);
71
+ expect(data.publication).toEqual({
72
+ ...pub,
73
+ profile_url: `https://app.dimensions.ai/details/publication/${pub.id}`,
74
+ });
72
75
  });
73
76
 
74
77
  it("returns not-found when no publication matches", async () => {
@@ -134,7 +137,10 @@ describe("lookup tools", () => {
134
137
  const data = parseToolResult(result);
135
138
 
136
139
  expect(data.found).toBe(true);
137
- expect(data.publication).toEqual(pub);
140
+ expect(data.publication).toEqual({
141
+ ...pub,
142
+ profile_url: `https://app.dimensions.ai/details/publication/${pub.id}`,
143
+ });
138
144
  });
139
145
 
140
146
  it("returns not-found for unknown PMID", async () => {
@@ -158,7 +164,7 @@ describe("lookup tools", () => {
158
164
  });
159
165
 
160
166
  const dsl = client.rawQuery.mock.calls[0][0] as string;
161
- expect(dsl).toContain("publications[id+title+times_cited]");
167
+ expect(dsl).toContain("publications[id+doi+title+times_cited]");
162
168
  });
163
169
 
164
170
  it("rejects invalid field names", async () => {
@@ -211,7 +217,10 @@ describe("lookup tools", () => {
211
217
  const data = parseToolResult(result);
212
218
 
213
219
  expect(data.found).toBe(true);
214
- expect(data.entity).toEqual(entity);
220
+ expect(data.entity).toEqual({
221
+ ...entity,
222
+ profile_url: "https://app.dimensions.ai/details/patent/pat.789",
223
+ });
215
224
  expect(data.entityType).toBe("patents");
216
225
  });
217
226
 
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Tests for construct_profile_url.
3
+ * @module test/tools/profile-url
4
+ */
5
+
6
+ import { beforeEach, describe, expect, it } from "vitest";
7
+ import { registerProfileUrlTool } from "../../src/mcp/tools/profile-url.js";
8
+ import { callTool, createMockServer, parseToolResult } from "../helpers/tool-test-harness.js";
9
+
10
+ describe("construct_profile_url", () => {
11
+ let handlers: ReturnType<typeof createMockServer>["handlers"];
12
+
13
+ beforeEach(() => {
14
+ const { server, handlers: h } = createMockServer();
15
+ handlers = h;
16
+ registerProfileUrlTool(server as never);
17
+ });
18
+
19
+ it("constructs a researcher profile URL", async () => {
20
+ const result = await callTool(handlers, "construct_profile_url", {
21
+ entityType: "researchers",
22
+ id: "ur.01222634304.39",
23
+ });
24
+ const data = parseToolResult(result);
25
+
26
+ expect(result.isError).toBeUndefined();
27
+ expect(data).toEqual({
28
+ entityType: "researchers",
29
+ id: "ur.01222634304.39",
30
+ profile_url:
31
+ "https://app.dimensions.ai/details/entities/publication/author/ur.01222634304.39",
32
+ });
33
+ });
34
+
35
+ it("constructs an organization profile URL", async () => {
36
+ const result = await callTool(handlers, "construct_profile_url", {
37
+ entityType: "organizations",
38
+ id: "grid.168010.e",
39
+ });
40
+ expect(parseToolResult(result).profile_url).toBe(
41
+ "https://app.dimensions.ai/details/organization/grid.168010.e",
42
+ );
43
+ });
44
+ });
@@ -90,6 +90,30 @@ describe("search tools", () => {
90
90
  expect(dsl).toContain('type = "article"');
91
91
  expect(dsl).toContain("limit 50");
92
92
  expect(dsl).toContain("sort by times_cited desc");
93
+ expect(dsl).toContain("publications[id+doi+title]");
94
+ });
95
+
96
+ it("always requests id and doi even when fields omit them", async () => {
97
+ client.rawQuery.mockResolvedValue(apiRows("publications", [], 0));
98
+
99
+ await callTool(handlers, "search_publications", {
100
+ query: "Joerg Sixt",
101
+ fields: ["title", "year"],
102
+ });
103
+
104
+ const dsl = client.rawQuery.mock.calls[0][0] as string;
105
+ expect(dsl).toContain("return publications[id+doi+title+year]");
106
+ });
107
+
108
+ it("requests basics plus identity fields when fields are omitted", async () => {
109
+ client.rawQuery.mockResolvedValue(apiRows("publications", [], 0));
110
+
111
+ await callTool(handlers, "search_publications", {
112
+ query: "Joerg Sixt",
113
+ });
114
+
115
+ const dsl = client.rawQuery.mock.calls[0][0] as string;
116
+ expect(dsl).toContain("return publications[basics+id+doi]");
93
117
  });
94
118
 
95
119
  it("formats result correctly", async () => {
@@ -107,7 +131,18 @@ describe("search tools", () => {
107
131
 
108
132
  expect(parsed.totalCount).toBe(2);
109
133
  expect(parsed.returnedCount).toBe(2);
110
- expect(parsed.publications).toEqual(mockPublications);
134
+ expect(parsed.publications).toEqual([
135
+ {
136
+ id: "pub1",
137
+ title: "Paper A",
138
+ profile_url: "https://app.dimensions.ai/details/publication/pub1",
139
+ },
140
+ {
141
+ id: "pub2",
142
+ title: "Paper B",
143
+ profile_url: "https://app.dimensions.ai/details/publication/pub2",
144
+ },
145
+ ]);
111
146
  });
112
147
 
113
148
  it("uses default limit when not specified", async () => {
@@ -161,7 +196,13 @@ describe("search tools", () => {
161
196
 
162
197
  const parsed = parseToolResult(result);
163
198
  expect(parsed.totalCount).toBe(1);
164
- expect(parsed.grants).toEqual(mockGrants);
199
+ expect(parsed.grants).toEqual([
200
+ {
201
+ id: "g1",
202
+ title: "Cancer Research Grant",
203
+ profile_url: "https://app.dimensions.ai/details/grant/g1",
204
+ },
205
+ ]);
165
206
  });
166
207
 
167
208
  it("resolves funder acronyms in funderOrgName", async () => {
@@ -210,7 +251,14 @@ describe("search tools", () => {
210
251
  });
211
252
 
212
253
  const parsed = parseToolResult(result);
213
- expect(parsed.researchers).toEqual(mockResearchers);
254
+ expect(parsed.researchers).toEqual([
255
+ {
256
+ id: "r1",
257
+ first_name: "Jennifer",
258
+ last_name: "Doudna",
259
+ profile_url: "https://app.dimensions.ai/details/entities/publication/author/r1",
260
+ },
261
+ ]);
214
262
  });
215
263
  });
216
264
 
@@ -74,7 +74,7 @@ describe("similar_documents tool", () => {
74
74
  expect(dsl).toContain("year >= 2018");
75
75
  expect(dsl).toContain("year <= 2024");
76
76
  expect(dsl).toContain("limit 5");
77
- expect(dsl).toContain("return publications[id+title+year]");
77
+ expect(dsl).toContain("return publications[id+doi+title+year]");
78
78
  });
79
79
 
80
80
  it("uses start_year for grants year filters", async () => {
@@ -112,7 +112,18 @@ describe("similar_documents tool", () => {
112
112
  expect(data.entityType).toBe("publications");
113
113
  expect(data.totalCount).toBe(2);
114
114
  expect(data.returnedCount).toBe(2);
115
- expect(data.publications).toEqual(pubs);
115
+ expect(data.publications).toEqual([
116
+ {
117
+ id: "pub.1",
118
+ title: "Paper A",
119
+ profile_url: "https://app.dimensions.ai/details/publication/pub.1",
120
+ },
121
+ {
122
+ id: "pub.2",
123
+ title: "Paper B",
124
+ profile_url: "https://app.dimensions.ai/details/publication/pub.2",
125
+ },
126
+ ]);
116
127
  });
117
128
 
118
129
  it("formats grant results", async () => {
@@ -127,7 +138,13 @@ describe("similar_documents tool", () => {
127
138
  const data = parseToolResult(result);
128
139
 
129
140
  expect(data.entityType).toBe("grants");
130
- expect(data.grants).toEqual(grants);
141
+ expect(data.grants).toEqual([
142
+ {
143
+ id: "grant.1",
144
+ title: "Grant A",
145
+ profile_url: "https://app.dimensions.ai/details/grant/grant.1",
146
+ },
147
+ ]);
131
148
  expect(data.publications).toBeUndefined();
132
149
  });
133
150
 
@@ -1 +1 @@
1
- {"root":["./src/http-main.ts","./src/index.ts","./src/main.ts","./src/client/command.ts","./src/client/config.ts","./src/client/deployment-config.ts","./src/client/errors.ts","./src/client/http-client.ts","./src/client/index.ts","./src/client/internal-dsl-client.ts","./src/client/rate-limiter.ts","./src/client/request-retry.ts","./src/client/resolve-api-key-user.ts","./src/client/types.ts","./src/client/usage-context.ts","./src/client/usage-headers.ts","./src/client/auth/config-loaders.ts","./src/client/auth/factory.ts","./src/client/auth/index.ts","./src/client/auth/jwt-utils.ts","./src/client/auth/node.ts","./src/client/auth/schemas.ts","./src/client/auth/types.ts","./src/client/auth/decorators/index.ts","./src/client/auth/decorators/with-caching.ts","./src/client/auth/providers/index.ts","./src/client/auth/providers/jwt-auth-provider.ts","./src/client/config/index.ts","./src/client/config/loader.ts","./src/client/config/node.ts","./src/client/config/schemas.ts","./src/client/config/types.ts","./src/client/config/unified-loader.ts","./src/dsl/client.ts","./src/dsl/create-client.ts","./src/dsl/fluent-query-builder.ts","./src/dsl/index.ts","./src/dsl/pagination.ts","./src/dsl/query-builder.ts","./src/dsl/response-parser.ts","./src/dsl/usage-policy.ts","./src/dsl/commands/apply-filters.ts","./src/dsl/commands/index.ts","./src/dsl/commands/validate-input.ts","./src/dsl/commands/facet/index.ts","./src/dsl/commands/facet/schemas.ts","./src/dsl/commands/functions/ClassifyCommand.ts","./src/dsl/commands/functions/ExtractAffiliationsCommand.ts","./src/dsl/commands/functions/ExtractConceptsCommand.ts","./src/dsl/commands/functions/ExtractGrantsCommand.ts","./src/dsl/commands/functions/index.ts","./src/dsl/schema/cache.ts","./src/dsl/schema/extract.ts","./src/dsl/schema/index.ts","./src/dsl/schema/load.ts","./src/dsl/schema/store.ts","./src/dsl/schema/structured-entities.ts","./src/dsl/schema/summary.ts","./src/dsl/schema/types.ts","./src/dsl/schema/validation.ts","./src/dsl/types/buckets.ts","./src/dsl/types/common.ts","./src/dsl/types/entities.ts","./src/dsl/types/index.ts","./src/dsl/types/return-clauses.ts","./src/dsl/types/special-functions.ts","./src/dsl/types/vocabulary.ts","./src/dsl/utils/escape.ts","./src/dsl/utils/sanitize.ts","./src/dsl/utils/string-utils.ts","./src/dsl/utils/validate-field-name.ts","./src/mcp/auto-update.ts","./src/mcp/batch-fetch.ts","./src/mcp/export-format.ts","./src/mcp/funder-org-names.ts","./src/mcp/http-server.ts","./src/mcp/server.ts","./src/mcp/shared-schema.ts","./src/mcp/usage-tracking.ts","./src/mcp/utils.ts","./src/mcp/examples/dsl-examples.ts","./src/mcp/examples/usage-scenarios.ts","./src/mcp/middleware/field-aliases.ts","./src/mcp/resources/schema.ts","./src/mcp/schema/context.ts","./src/mcp/schema/index.ts","./src/mcp/tools/analytics-filters.ts","./src/mcp/tools/analytics.ts","./src/mcp/tools/fetch-search-pages.ts","./src/mcp/tools/functions.ts","./src/mcp/tools/lookup.ts","./src/mcp/tools/query.ts","./src/mcp/tools/schema.ts","./src/mcp/tools/search-entity-metadata.ts","./src/mcp/tools/search-input.ts","./src/mcp/tools/search.ts","./src/mcp/tools/similar-documents.ts"],"version":"5.9.3"}
1
+ {"root":["./src/http-main.ts","./src/index.ts","./src/main.ts","./src/client/command.ts","./src/client/config.ts","./src/client/deployment-config.ts","./src/client/errors.ts","./src/client/http-client.ts","./src/client/index.ts","./src/client/internal-dsl-client.ts","./src/client/rate-limiter.ts","./src/client/request-retry.ts","./src/client/resolve-api-key-user.ts","./src/client/types.ts","./src/client/usage-context.ts","./src/client/usage-headers.ts","./src/client/auth/config-loaders.ts","./src/client/auth/factory.ts","./src/client/auth/index.ts","./src/client/auth/jwt-utils.ts","./src/client/auth/node.ts","./src/client/auth/schemas.ts","./src/client/auth/types.ts","./src/client/auth/decorators/index.ts","./src/client/auth/decorators/with-caching.ts","./src/client/auth/providers/index.ts","./src/client/auth/providers/jwt-auth-provider.ts","./src/client/config/index.ts","./src/client/config/loader.ts","./src/client/config/node.ts","./src/client/config/schemas.ts","./src/client/config/types.ts","./src/client/config/unified-loader.ts","./src/dsl/client.ts","./src/dsl/create-client.ts","./src/dsl/fluent-query-builder.ts","./src/dsl/index.ts","./src/dsl/pagination.ts","./src/dsl/query-builder.ts","./src/dsl/response-parser.ts","./src/dsl/usage-policy.ts","./src/dsl/commands/apply-filters.ts","./src/dsl/commands/index.ts","./src/dsl/commands/validate-input.ts","./src/dsl/commands/facet/index.ts","./src/dsl/commands/facet/schemas.ts","./src/dsl/commands/functions/ClassifyCommand.ts","./src/dsl/commands/functions/ExtractAffiliationsCommand.ts","./src/dsl/commands/functions/ExtractConceptsCommand.ts","./src/dsl/commands/functions/ExtractGrantsCommand.ts","./src/dsl/commands/functions/index.ts","./src/dsl/schema/cache.ts","./src/dsl/schema/extract.ts","./src/dsl/schema/index.ts","./src/dsl/schema/load.ts","./src/dsl/schema/store.ts","./src/dsl/schema/structured-entities.ts","./src/dsl/schema/summary.ts","./src/dsl/schema/types.ts","./src/dsl/schema/validation.ts","./src/dsl/types/buckets.ts","./src/dsl/types/common.ts","./src/dsl/types/entities.ts","./src/dsl/types/index.ts","./src/dsl/types/return-clauses.ts","./src/dsl/types/special-functions.ts","./src/dsl/types/vocabulary.ts","./src/dsl/utils/escape.ts","./src/dsl/utils/sanitize.ts","./src/dsl/utils/string-utils.ts","./src/dsl/utils/validate-field-name.ts","./src/mcp/auto-update.ts","./src/mcp/batch-fetch.ts","./src/mcp/export-format.ts","./src/mcp/funder-org-names.ts","./src/mcp/http-server.ts","./src/mcp/identity-fields.ts","./src/mcp/profile-urls.ts","./src/mcp/server.ts","./src/mcp/shared-schema.ts","./src/mcp/usage-tracking.ts","./src/mcp/utils.ts","./src/mcp/examples/dsl-examples.ts","./src/mcp/examples/usage-scenarios.ts","./src/mcp/middleware/field-aliases.ts","./src/mcp/resources/schema.ts","./src/mcp/schema/context.ts","./src/mcp/schema/index.ts","./src/mcp/tools/analytics-filters.ts","./src/mcp/tools/analytics.ts","./src/mcp/tools/fetch-search-pages.ts","./src/mcp/tools/functions.ts","./src/mcp/tools/lookup.ts","./src/mcp/tools/profile-url.ts","./src/mcp/tools/query.ts","./src/mcp/tools/schema.ts","./src/mcp/tools/search-entity-metadata.ts","./src/mcp/tools/search-input.ts","./src/mcp/tools/search.ts","./src/mcp/tools/similar-documents.ts"],"version":"5.9.3"}