@digital-science-dsl/dimensions-analytics-mcp 1.3.1 → 1.4.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 +13 -1
- package/dist/client/config.d.ts +14 -9
- package/dist/client/config.d.ts.map +1 -1
- package/dist/client/config.js +17 -31
- package/dist/client/config.js.map +1 -1
- package/dist/client/deployment-config.d.ts.map +1 -1
- package/dist/client/deployment-config.js +6 -2
- package/dist/client/deployment-config.js.map +1 -1
- package/dist/dsl/client.d.ts.map +1 -1
- package/dist/dsl/client.js +4 -1
- package/dist/dsl/client.js.map +1 -1
- package/dist/mcp/batch-fetch.d.ts.map +1 -1
- package/dist/mcp/batch-fetch.js +2 -1
- package/dist/mcp/batch-fetch.js.map +1 -1
- package/dist/mcp/identity-fields.d.ts +22 -0
- package/dist/mcp/identity-fields.d.ts.map +1 -0
- package/dist/mcp/identity-fields.js +62 -0
- package/dist/mcp/identity-fields.js.map +1 -0
- package/dist/mcp/profile-urls.d.ts +93 -0
- package/dist/mcp/profile-urls.d.ts.map +1 -0
- package/dist/mcp/profile-urls.js +141 -0
- package/dist/mcp/profile-urls.js.map +1 -0
- package/dist/mcp/resources/schema.d.ts.map +1 -1
- package/dist/mcp/resources/schema.js +13 -0
- package/dist/mcp/resources/schema.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +5 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/analytics.d.ts.map +1 -1
- package/dist/mcp/tools/analytics.js +7 -4
- package/dist/mcp/tools/analytics.js.map +1 -1
- package/dist/mcp/tools/fetch-search-pages.d.ts.map +1 -1
- package/dist/mcp/tools/fetch-search-pages.js +2 -1
- package/dist/mcp/tools/fetch-search-pages.js.map +1 -1
- package/dist/mcp/tools/lookup.d.ts.map +1 -1
- package/dist/mcp/tools/lookup.js +14 -12
- package/dist/mcp/tools/lookup.js.map +1 -1
- package/dist/mcp/tools/profile-url.d.ts +11 -0
- package/dist/mcp/tools/profile-url.d.ts.map +1 -0
- package/dist/mcp/tools/profile-url.js +56 -0
- package/dist/mcp/tools/profile-url.js.map +1 -0
- package/dist/mcp/tools/search-entity-metadata.d.ts.map +1 -1
- package/dist/mcp/tools/search-entity-metadata.js +4 -2
- package/dist/mcp/tools/search-entity-metadata.js.map +1 -1
- package/dist/mcp/tools/search-input.js +1 -1
- package/dist/mcp/tools/search-input.js.map +1 -1
- package/dist/mcp/tools/search.d.ts.map +1 -1
- package/dist/mcp/tools/search.js +4 -5
- package/dist/mcp/tools/search.js.map +1 -1
- package/dist/mcp/tools/similar-documents.d.ts.map +1 -1
- package/dist/mcp/tools/similar-documents.js +4 -5
- package/dist/mcp/tools/similar-documents.js.map +1 -1
- package/package.json +3 -3
- package/src/client/config.ts +29 -41
- package/src/client/deployment-config.ts +7 -2
- package/src/dsl/client.ts +6 -3
- package/src/mcp/batch-fetch.ts +2 -1
- package/src/mcp/identity-fields.ts +65 -0
- package/src/mcp/profile-urls.ts +180 -0
- package/src/mcp/resources/schema.ts +20 -0
- package/src/mcp/server.ts +5 -1
- package/src/mcp/tools/analytics.ts +7 -4
- package/src/mcp/tools/fetch-search-pages.ts +2 -1
- package/src/mcp/tools/lookup.ts +14 -9
- package/src/mcp/tools/profile-url.ts +70 -0
- package/src/mcp/tools/search-entity-metadata.ts +4 -2
- package/src/mcp/tools/search-input.ts +1 -1
- package/src/mcp/tools/search.ts +4 -5
- package/src/mcp/tools/similar-documents.ts +4 -5
- package/test/client/config-schema.test.ts +46 -0
- package/test/client/deployment-config.test.ts +11 -0
- package/test/dsl/client.test.ts +22 -0
- package/test/identity-fields.test.ts +44 -0
- package/test/profile-urls.test.ts +151 -0
- package/test/resources/schema.test.ts +1 -0
- package/test/server.test.ts +5 -0
- package/test/tools/analytics.test.ts +31 -1
- package/test/tools/fetch-search-pages.test.ts +10 -4
- package/test/tools/lookup.test.ts +14 -5
- package/test/tools/profile-url.test.ts +44 -0
- package/test/tools/search.test.ts +51 -3
- package/test/tools/similar-documents.test.ts +20 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for always-on identity fields (WEBAPPDEV-13816).
|
|
3
|
+
* @module test/identity-fields
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { identityFieldsFor, mergeIdentityFields } from "../src/mcp/identity-fields.js";
|
|
8
|
+
|
|
9
|
+
describe("identityFieldsFor", () => {
|
|
10
|
+
it("requires id and doi for publications", () => {
|
|
11
|
+
expect(identityFieldsFor("publications")).toEqual(["id", "doi"]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("requires id for other searchable entity types", () => {
|
|
15
|
+
expect(identityFieldsFor("grants")).toEqual(["id"]);
|
|
16
|
+
expect(identityFieldsFor("researchers")).toEqual(["id"]);
|
|
17
|
+
expect(identityFieldsFor("organizations")).toEqual(["id"]);
|
|
18
|
+
expect(identityFieldsFor("patents")).toEqual(["id"]);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("mergeIdentityFields", () => {
|
|
23
|
+
it("uses basics plus identity fields when the caller omits fields", () => {
|
|
24
|
+
expect(mergeIdentityFields("publications")).toEqual(["basics", "id", "doi"]);
|
|
25
|
+
expect(mergeIdentityFields("grants")).toEqual(["basics", "id"]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("prepends missing identity fields to an explicit field list", () => {
|
|
29
|
+
expect(mergeIdentityFields("publications", ["title", "year"])).toEqual([
|
|
30
|
+
"id",
|
|
31
|
+
"doi",
|
|
32
|
+
"title",
|
|
33
|
+
"year",
|
|
34
|
+
]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("does not duplicate identity fields the caller already requested", () => {
|
|
38
|
+
expect(mergeIdentityFields("publications", ["id", "title", "doi"])).toEqual([
|
|
39
|
+
"id",
|
|
40
|
+
"doi",
|
|
41
|
+
"title",
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -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);
|
package/test/server.test.ts
CHANGED
|
@@ -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(
|
|
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({
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -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"}
|