@digital-science-dsl/dimensions-analytics-mcp 1.0.4 → 1.2.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.
- package/CHANGELOG.md +12 -0
- package/dist/dsl/schema/structured-entities.d.ts +1 -1
- package/dist/dsl/schema/structured-entities.d.ts.map +1 -1
- package/dist/dsl/schema/structured-entities.js +4 -0
- package/dist/dsl/schema/structured-entities.js.map +1 -1
- package/dist/dsl/types/vocabulary.d.ts +2 -2
- package/dist/dsl/types/vocabulary.d.ts.map +1 -1
- package/dist/dsl/types/vocabulary.js +5 -0
- package/dist/dsl/types/vocabulary.js.map +1 -1
- package/dist/mcp/examples/dsl-examples.d.ts.map +1 -1
- package/dist/mcp/examples/dsl-examples.js +14 -0
- package/dist/mcp/examples/dsl-examples.js.map +1 -1
- package/dist/mcp/examples/usage-scenarios.d.ts.map +1 -1
- package/dist/mcp/examples/usage-scenarios.js +19 -0
- package/dist/mcp/examples/usage-scenarios.js.map +1 -1
- package/dist/mcp/middleware/field-aliases.d.ts.map +1 -1
- package/dist/mcp/middleware/field-aliases.js +4 -0
- package/dist/mcp/middleware/field-aliases.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +8 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/analytics-filters.d.ts.map +1 -1
- package/dist/mcp/tools/analytics-filters.js +2 -0
- package/dist/mcp/tools/analytics-filters.js.map +1 -1
- package/dist/mcp/tools/lookup.js +1 -1
- package/dist/mcp/tools/lookup.js.map +1 -1
- package/dist/mcp/tools/search-entity-metadata.d.ts.map +1 -1
- package/dist/mcp/tools/search-entity-metadata.js +46 -0
- package/dist/mcp/tools/search-entity-metadata.js.map +1 -1
- package/dist/mcp/tools/similar-documents.d.ts +26 -0
- package/dist/mcp/tools/similar-documents.d.ts.map +1 -0
- package/dist/mcp/tools/similar-documents.js +171 -0
- package/dist/mcp/tools/similar-documents.js.map +1 -0
- package/package.json +1 -1
- package/src/dsl/schema/structured-entities.ts +4 -0
- package/src/dsl/types/vocabulary.ts +12 -2
- package/src/mcp/examples/dsl-examples.ts +14 -0
- package/src/mcp/examples/usage-scenarios.ts +19 -0
- package/src/mcp/middleware/field-aliases.ts +4 -0
- package/src/mcp/server.ts +8 -2
- package/src/mcp/tools/analytics-filters.ts +2 -0
- package/src/mcp/tools/lookup.ts +1 -1
- package/src/mcp/tools/search-entity-metadata.ts +50 -0
- package/src/mcp/tools/similar-documents.ts +232 -0
- package/test/examples/usage-scenarios.test.ts +5 -3
- package/test/fixtures/describe-schema.json +74 -0
- package/test/integration/suites/index.ts +2 -0
- package/test/integration/suites/search.integration.ts +24 -0
- package/test/integration/suites/similar-documents.integration.ts +51 -0
- package/test/schema/store.test.ts +5 -0
- package/test/server.test.ts +5 -0
- package/test/tools/search.test.ts +42 -2
- package/test/tools/similar-documents.test.ts +173 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -8,6 +8,7 @@ import { registerAnalyticsTools } from "../../src/mcp/tools/analytics.js";
|
|
|
8
8
|
import { registerLookupTools } from "../../src/mcp/tools/lookup.js";
|
|
9
9
|
import { registerQueryTools } from "../../src/mcp/tools/query.js";
|
|
10
10
|
import { registerSearchTools } from "../../src/mcp/tools/search.js";
|
|
11
|
+
import { registerSimilarDocumentsTool } from "../../src/mcp/tools/similar-documents.js";
|
|
11
12
|
import { testSchemaStore } from "../helpers/schema-fixture.js";
|
|
12
13
|
import {
|
|
13
14
|
callTool,
|
|
@@ -34,11 +35,12 @@ describe("usage-scenarios", () => {
|
|
|
34
35
|
registerLookupTools(server as never, client as never);
|
|
35
36
|
registerAnalyticsTools(server as never, client as never, schemaStore);
|
|
36
37
|
registerQueryTools(server as never, client as never, schemaStore);
|
|
38
|
+
registerSimilarDocumentsTool(server as never, client as never);
|
|
37
39
|
});
|
|
38
40
|
|
|
39
|
-
it("documents
|
|
40
|
-
expect(USAGE_SCENARIOS).toHaveLength(
|
|
41
|
-
expect(new Set(USAGE_SCENARIOS.map((s) => s.id)).size).toBe(
|
|
41
|
+
it("documents twelve natural-language workflows", () => {
|
|
42
|
+
expect(USAGE_SCENARIOS).toHaveLength(12);
|
|
43
|
+
expect(new Set(USAGE_SCENARIOS.map((s) => s.id)).size).toBe(12);
|
|
42
44
|
});
|
|
43
45
|
|
|
44
46
|
for (const scenario of USAGE_SCENARIOS) {
|
|
@@ -3801,6 +3801,80 @@
|
|
|
3801
3801
|
"fieldsets": ["basics", "categories"],
|
|
3802
3802
|
"metrics": { "count": { "name": "count", "description": "Total count" } },
|
|
3803
3803
|
"search_fields": ["full_data", "title_abstract_only", "concepts"]
|
|
3804
|
+
},
|
|
3805
|
+
"funder_groups": {
|
|
3806
|
+
"fields": {
|
|
3807
|
+
"id": {
|
|
3808
|
+
"type": "string",
|
|
3809
|
+
"description": "The Dimensions ID of the group.",
|
|
3810
|
+
"long_description": null,
|
|
3811
|
+
"is_entity": false,
|
|
3812
|
+
"is_filter": true,
|
|
3813
|
+
"is_facet": false,
|
|
3814
|
+
"is_multivalue": false,
|
|
3815
|
+
"solr_fields": { "return": "id", "filter": "id" }
|
|
3816
|
+
},
|
|
3817
|
+
"name": {
|
|
3818
|
+
"type": "string",
|
|
3819
|
+
"description": "The name of the group.",
|
|
3820
|
+
"long_description": null,
|
|
3821
|
+
"is_entity": false,
|
|
3822
|
+
"is_filter": true,
|
|
3823
|
+
"is_facet": false,
|
|
3824
|
+
"is_multivalue": false,
|
|
3825
|
+
"solr_fields": { "return": "name", "filter": "name" }
|
|
3826
|
+
},
|
|
3827
|
+
"organization_ids": {
|
|
3828
|
+
"type": "string",
|
|
3829
|
+
"description": "The GRID IDs of the organizations which are members of this group.",
|
|
3830
|
+
"long_description": null,
|
|
3831
|
+
"is_entity": false,
|
|
3832
|
+
"is_filter": true,
|
|
3833
|
+
"is_facet": false,
|
|
3834
|
+
"is_multivalue": true,
|
|
3835
|
+
"solr_fields": { "return": "values", "filter": "values" }
|
|
3836
|
+
}
|
|
3837
|
+
},
|
|
3838
|
+
"fieldsets": ["basics"],
|
|
3839
|
+
"metrics": { "count": { "name": "count", "description": "Total count" } },
|
|
3840
|
+
"search_fields": ["name"]
|
|
3841
|
+
},
|
|
3842
|
+
"research_org_groups": {
|
|
3843
|
+
"fields": {
|
|
3844
|
+
"id": {
|
|
3845
|
+
"type": "string",
|
|
3846
|
+
"description": "The Dimensions ID of the group.",
|
|
3847
|
+
"long_description": null,
|
|
3848
|
+
"is_entity": false,
|
|
3849
|
+
"is_filter": true,
|
|
3850
|
+
"is_facet": false,
|
|
3851
|
+
"is_multivalue": false,
|
|
3852
|
+
"solr_fields": { "return": "id", "filter": "id" }
|
|
3853
|
+
},
|
|
3854
|
+
"name": {
|
|
3855
|
+
"type": "string",
|
|
3856
|
+
"description": "The name of the group.",
|
|
3857
|
+
"long_description": null,
|
|
3858
|
+
"is_entity": false,
|
|
3859
|
+
"is_filter": true,
|
|
3860
|
+
"is_facet": false,
|
|
3861
|
+
"is_multivalue": false,
|
|
3862
|
+
"solr_fields": { "return": "name", "filter": "name" }
|
|
3863
|
+
},
|
|
3864
|
+
"organization_ids": {
|
|
3865
|
+
"type": "string",
|
|
3866
|
+
"description": "The GRID IDs of the organizations which are members of this group.",
|
|
3867
|
+
"long_description": null,
|
|
3868
|
+
"is_entity": false,
|
|
3869
|
+
"is_filter": true,
|
|
3870
|
+
"is_facet": false,
|
|
3871
|
+
"is_multivalue": true,
|
|
3872
|
+
"solr_fields": { "return": "values", "filter": "values" }
|
|
3873
|
+
}
|
|
3874
|
+
},
|
|
3875
|
+
"fieldsets": ["basics"],
|
|
3876
|
+
"metrics": { "count": { "name": "count", "description": "Total count" } },
|
|
3877
|
+
"search_fields": ["name"]
|
|
3804
3878
|
}
|
|
3805
3879
|
},
|
|
3806
3880
|
"entities": {
|
|
@@ -10,6 +10,7 @@ import { lookupSuite } from "./lookup.integration.js";
|
|
|
10
10
|
import { querySuite } from "./query.integration.js";
|
|
11
11
|
import { resourcesSuite } from "./resources.integration.js";
|
|
12
12
|
import { searchSuite } from "./search.integration.js";
|
|
13
|
+
import { similarDocumentsSuite } from "./similar-documents.integration.js";
|
|
13
14
|
|
|
14
15
|
/** All integration test suites, in execution order. */
|
|
15
16
|
export const allSuites: readonly EvalSuite[] = [
|
|
@@ -17,6 +18,7 @@ export const allSuites: readonly EvalSuite[] = [
|
|
|
17
18
|
lookupSuite,
|
|
18
19
|
querySuite,
|
|
19
20
|
functionsSuite,
|
|
21
|
+
similarDocumentsSuite,
|
|
20
22
|
analyticsSuite,
|
|
21
23
|
resourcesSuite,
|
|
22
24
|
];
|
|
@@ -76,5 +76,29 @@ export const searchSuite: EvalSuite = {
|
|
|
76
76
|
args: { query: "Massachusetts Institute of Technology", limit: 5 },
|
|
77
77
|
assertions: [isSuccess(), hasField("totalCount"), arrayMinLength("organizations", 1)],
|
|
78
78
|
},
|
|
79
|
+
{
|
|
80
|
+
name: "search reports for climate",
|
|
81
|
+
tool: "search_reports",
|
|
82
|
+
args: { query: "climate change", limit: 5 },
|
|
83
|
+
assertions: [isSuccess(), hasField("totalCount"), arrayMinLength("reports", 1)],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "search source titles for Nature",
|
|
87
|
+
tool: "search_source_titles",
|
|
88
|
+
args: { query: "Nature", limit: 5 },
|
|
89
|
+
assertions: [isSuccess(), hasField("totalCount"), arrayMinLength("source_titles", 1)],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "search funder groups for NIH",
|
|
93
|
+
tool: "search_funder_groups",
|
|
94
|
+
args: { query: "NIH", limit: 5 },
|
|
95
|
+
assertions: [isSuccess(), hasField("totalCount"), arrayMinLength("funder_groups", 1)],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "search research org groups",
|
|
99
|
+
tool: "search_research_org_groups",
|
|
100
|
+
args: { query: "University", limit: 5 },
|
|
101
|
+
assertions: [isSuccess(), hasField("totalCount"), arrayMinLength("research_org_groups", 1)],
|
|
102
|
+
},
|
|
79
103
|
],
|
|
80
104
|
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eval suite for similar_documents tool.
|
|
3
|
+
* @module test/integration/suites/similar-documents
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { arrayMinLength, fieldAtLeast, hasField, isSuccess } from "../assertions.js";
|
|
7
|
+
import type { EvalSuite } from "../types.js";
|
|
8
|
+
|
|
9
|
+
const SAMPLE_ABSTRACT =
|
|
10
|
+
"After spinal cord injury, macrophages infiltrate the lesion site and contribute to both tissue damage and repair processes. Understanding macrophage polarization could lead to novel therapeutic strategies.";
|
|
11
|
+
|
|
12
|
+
const SAMPLE_GRANT_TEXT =
|
|
13
|
+
"Development of novel cancer immunotherapy approaches using checkpoint inhibitors and CAR-T cell engineering for solid tumors.";
|
|
14
|
+
|
|
15
|
+
export const similarDocumentsSuite: EvalSuite = {
|
|
16
|
+
name: "similar_documents",
|
|
17
|
+
description: "Concept-based similar document search for publications and grants",
|
|
18
|
+
cases: [
|
|
19
|
+
{
|
|
20
|
+
name: "similar publications from abstract text",
|
|
21
|
+
tool: "similar_documents",
|
|
22
|
+
args: {
|
|
23
|
+
entityType: "publications",
|
|
24
|
+
text: SAMPLE_ABSTRACT,
|
|
25
|
+
yearFrom: 2016,
|
|
26
|
+
limit: 5,
|
|
27
|
+
fields: ["id", "title", "year"],
|
|
28
|
+
},
|
|
29
|
+
assertions: [
|
|
30
|
+
isSuccess(),
|
|
31
|
+
hasField("totalCount"),
|
|
32
|
+
fieldAtLeast("totalCount", 1),
|
|
33
|
+
arrayMinLength("publications", 1),
|
|
34
|
+
],
|
|
35
|
+
timeout: 60_000,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "similar grants from description text",
|
|
39
|
+
tool: "similar_documents",
|
|
40
|
+
args: {
|
|
41
|
+
entityType: "grants",
|
|
42
|
+
text: SAMPLE_GRANT_TEXT,
|
|
43
|
+
yearFrom: 2020,
|
|
44
|
+
limit: 5,
|
|
45
|
+
fields: ["id", "title"],
|
|
46
|
+
},
|
|
47
|
+
assertions: [isSuccess(), hasField("totalCount"), arrayMinLength("grants", 1)],
|
|
48
|
+
timeout: 60_000,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
@@ -41,5 +41,10 @@ describe("SchemaStore", () => {
|
|
|
41
41
|
const types = store.structuredEntityTypes();
|
|
42
42
|
expect(types).toContain("publications");
|
|
43
43
|
expect(types).toContain("organizations");
|
|
44
|
+
expect(types).toContain("reports");
|
|
45
|
+
expect(types).toContain("source_titles");
|
|
46
|
+
expect(types).toContain("funder_groups");
|
|
47
|
+
expect(types).toContain("research_org_groups");
|
|
48
|
+
expect(types).toHaveLength(12);
|
|
44
49
|
});
|
|
45
50
|
});
|
package/test/server.test.ts
CHANGED
|
@@ -60,6 +60,10 @@ describe("createMcpServerAsync", () => {
|
|
|
60
60
|
expect(instructions).toContain("search_researchers matches names only");
|
|
61
61
|
expect(instructions).toContain("research_orgs.id");
|
|
62
62
|
expect(instructions).toContain("funder_orgs");
|
|
63
|
+
expect(instructions).toContain("search_organizations for institution lookup");
|
|
64
|
+
expect(instructions).toContain("search_source_titles");
|
|
65
|
+
expect(instructions).toContain("search_reports");
|
|
66
|
+
expect(instructions).toContain("similar_documents");
|
|
63
67
|
expect(instructions).toContain("fetch_search_pages");
|
|
64
68
|
expect(instructions).toContain("dimensions://schema/policy");
|
|
65
69
|
});
|
|
@@ -134,6 +138,7 @@ describe("MCP protocol", () => {
|
|
|
134
138
|
const toolNames = tools.tools.map((t) => t.name);
|
|
135
139
|
expect(toolNames).toContain("search_publications");
|
|
136
140
|
expect(toolNames).toContain("fetch_search_pages");
|
|
141
|
+
expect(toolNames).toContain("similar_documents");
|
|
137
142
|
expect(toolNames).toContain("execute_dsl");
|
|
138
143
|
expect(toolNames).not.toContain("validate_dsl");
|
|
139
144
|
|
|
@@ -37,7 +37,7 @@ describe("search tools", () => {
|
|
|
37
37
|
registerSearchTools(server as never, client as never, testSchemaStore());
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
it("registers all
|
|
40
|
+
it("registers all 12 search tools", () => {
|
|
41
41
|
const expectedTools = [
|
|
42
42
|
"search_publications",
|
|
43
43
|
"search_grants",
|
|
@@ -47,11 +47,15 @@ describe("search tools", () => {
|
|
|
47
47
|
"search_datasets",
|
|
48
48
|
"search_policy_documents",
|
|
49
49
|
"search_organizations",
|
|
50
|
+
"search_reports",
|
|
51
|
+
"search_source_titles",
|
|
52
|
+
"search_funder_groups",
|
|
53
|
+
"search_research_org_groups",
|
|
50
54
|
];
|
|
51
55
|
for (const name of expectedTools) {
|
|
52
56
|
expect(handlers.has(name)).toBe(true);
|
|
53
57
|
}
|
|
54
|
-
expect(handlers.size).toBe(
|
|
58
|
+
expect(handlers.size).toBe(12);
|
|
55
59
|
});
|
|
56
60
|
|
|
57
61
|
describe("search_publications", () => {
|
|
@@ -243,6 +247,42 @@ describe("search tools", () => {
|
|
|
243
247
|
});
|
|
244
248
|
});
|
|
245
249
|
|
|
250
|
+
describe("search_reports", () => {
|
|
251
|
+
it("applies year filters and returns reports", async () => {
|
|
252
|
+
client.rawQuery.mockResolvedValue(apiRows("reports", [{ id: "rep.1", title: "Report" }], 1));
|
|
253
|
+
|
|
254
|
+
const result = await callTool(handlers, "search_reports", {
|
|
255
|
+
query: "climate",
|
|
256
|
+
yearFrom: 2020,
|
|
257
|
+
yearTo: 2024,
|
|
258
|
+
limit: 10,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const dsl = client.rawQuery.mock.calls[0][0] as string;
|
|
262
|
+
expect(dsl).toContain("search reports");
|
|
263
|
+
expect(dsl).toContain("year >= 2020");
|
|
264
|
+
expect(dsl).toContain("year <= 2024");
|
|
265
|
+
expect(parseToolResult(result).reports).toHaveLength(1);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe("search_funder_groups", () => {
|
|
270
|
+
it("searches funder groups by name", async () => {
|
|
271
|
+
client.rawQuery.mockResolvedValue(
|
|
272
|
+
apiRows("funder_groups", [{ id: "fg.1", name: "NIH Institutes" }], 1),
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const result = await callTool(handlers, "search_funder_groups", {
|
|
276
|
+
query: "NIH",
|
|
277
|
+
limit: 10,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const dsl = client.rawQuery.mock.calls[0][0] as string;
|
|
281
|
+
expect(dsl).toContain("search funder_groups");
|
|
282
|
+
expect(parseToolResult(result).funder_groups).toHaveLength(1);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
246
286
|
describe("truncation warnings", () => {
|
|
247
287
|
it("includes truncation warning when totalCount exceeds returnedCount", async () => {
|
|
248
288
|
client.rawQuery.mockResolvedValue(
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the similar_documents MCP tool.
|
|
3
|
+
* @module test/tools/similar-documents
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
7
|
+
import { registerSimilarDocumentsTool } from "../../src/mcp/tools/similar-documents.js";
|
|
8
|
+
import {
|
|
9
|
+
callTool,
|
|
10
|
+
createMockClient,
|
|
11
|
+
createMockServer,
|
|
12
|
+
parseToolResult,
|
|
13
|
+
} from "../helpers/tool-test-harness.js";
|
|
14
|
+
|
|
15
|
+
/** API-shaped mock response for parseEntityResponse. */
|
|
16
|
+
function apiRows(
|
|
17
|
+
entity: string,
|
|
18
|
+
rows: Record<string, unknown>[],
|
|
19
|
+
totalCount?: number,
|
|
20
|
+
): Record<string, unknown> {
|
|
21
|
+
return {
|
|
22
|
+
[entity]: rows,
|
|
23
|
+
_stats: { total_count: totalCount ?? rows.length },
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const SAMPLE_ABSTRACT =
|
|
28
|
+
"After spinal cord injury, macrophages infiltrate the lesion site and contribute to both tissue damage and repair.";
|
|
29
|
+
|
|
30
|
+
describe("similar_documents tool", () => {
|
|
31
|
+
let handlers: ReturnType<typeof createMockServer>["handlers"];
|
|
32
|
+
let client: ReturnType<typeof createMockClient>;
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
const { server, handlers: h } = createMockServer();
|
|
36
|
+
client = createMockClient();
|
|
37
|
+
handlers = h;
|
|
38
|
+
registerSimilarDocumentsTool(server as never, client as never);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("registers similar_documents", () => {
|
|
42
|
+
expect(handlers.has("similar_documents")).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("builds similar_documents DSL with default score sort and limit 20", async () => {
|
|
46
|
+
client.rawQuery.mockResolvedValue(apiRows("publications", [{ id: "pub.1", title: "A" }], 1));
|
|
47
|
+
|
|
48
|
+
await callTool(handlers, "similar_documents", {
|
|
49
|
+
entityType: "publications",
|
|
50
|
+
text: SAMPLE_ABSTRACT,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(client.rawQuery).toHaveBeenCalledTimes(1);
|
|
54
|
+
const dsl = client.rawQuery.mock.calls[0][0] as string;
|
|
55
|
+
expect(dsl).toContain("search publications for similar_documents(");
|
|
56
|
+
expect(dsl).toContain(SAMPLE_ABSTRACT);
|
|
57
|
+
expect(dsl).toContain("sort by score desc");
|
|
58
|
+
expect(dsl).toContain("limit 20");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("applies year filters on publications and custom limit/fields", async () => {
|
|
62
|
+
client.rawQuery.mockResolvedValue(apiRows("publications", [], 0));
|
|
63
|
+
|
|
64
|
+
await callTool(handlers, "similar_documents", {
|
|
65
|
+
entityType: "publications",
|
|
66
|
+
text: SAMPLE_ABSTRACT,
|
|
67
|
+
yearFrom: 2018,
|
|
68
|
+
yearTo: 2024,
|
|
69
|
+
limit: 5,
|
|
70
|
+
fields: ["id", "title", "year"],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const dsl = client.rawQuery.mock.calls[0][0] as string;
|
|
74
|
+
expect(dsl).toContain("year >= 2018");
|
|
75
|
+
expect(dsl).toContain("year <= 2024");
|
|
76
|
+
expect(dsl).toContain("limit 5");
|
|
77
|
+
expect(dsl).toContain("return publications[id+title+year]");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("uses start_year for grants year filters", async () => {
|
|
81
|
+
client.rawQuery.mockResolvedValue(apiRows("grants", [{ id: "grant.1" }], 1));
|
|
82
|
+
|
|
83
|
+
await callTool(handlers, "similar_documents", {
|
|
84
|
+
entityType: "grants",
|
|
85
|
+
text: "Cancer immunotherapy CAR-T solid tumors",
|
|
86
|
+
yearFrom: 2020,
|
|
87
|
+
limit: 5,
|
|
88
|
+
fields: ["id", "title"],
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const dsl = client.rawQuery.mock.calls[0][0] as string;
|
|
92
|
+
expect(dsl).toContain("search grants for similar_documents(");
|
|
93
|
+
expect(dsl).toContain("start_year >= 2020");
|
|
94
|
+
expect(dsl).toContain("return grants[id+title]");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("formats publication results", async () => {
|
|
98
|
+
const pubs = [
|
|
99
|
+
{ id: "pub.1", title: "Paper A" },
|
|
100
|
+
{ id: "pub.2", title: "Paper B" },
|
|
101
|
+
];
|
|
102
|
+
client.rawQuery.mockResolvedValue(apiRows("publications", pubs, 2));
|
|
103
|
+
|
|
104
|
+
const result = await callTool(handlers, "similar_documents", {
|
|
105
|
+
entityType: "publications",
|
|
106
|
+
text: SAMPLE_ABSTRACT,
|
|
107
|
+
limit: 5,
|
|
108
|
+
});
|
|
109
|
+
const data = parseToolResult(result);
|
|
110
|
+
|
|
111
|
+
expect(result.isError).toBeUndefined();
|
|
112
|
+
expect(data.entityType).toBe("publications");
|
|
113
|
+
expect(data.totalCount).toBe(2);
|
|
114
|
+
expect(data.returnedCount).toBe(2);
|
|
115
|
+
expect(data.publications).toEqual(pubs);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("formats grant results", async () => {
|
|
119
|
+
const grants = [{ id: "grant.1", title: "Grant A" }];
|
|
120
|
+
client.rawQuery.mockResolvedValue(apiRows("grants", grants, 1));
|
|
121
|
+
|
|
122
|
+
const result = await callTool(handlers, "similar_documents", {
|
|
123
|
+
entityType: "grants",
|
|
124
|
+
text: "Climate adaptation funding",
|
|
125
|
+
limit: 5,
|
|
126
|
+
});
|
|
127
|
+
const data = parseToolResult(result);
|
|
128
|
+
|
|
129
|
+
expect(data.entityType).toBe("grants");
|
|
130
|
+
expect(data.grants).toEqual(grants);
|
|
131
|
+
expect(data.publications).toBeUndefined();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("applies extra filters", async () => {
|
|
135
|
+
client.rawQuery.mockResolvedValue(apiRows("publications", [], 0));
|
|
136
|
+
|
|
137
|
+
await callTool(handlers, "similar_documents", {
|
|
138
|
+
entityType: "publications",
|
|
139
|
+
text: SAMPLE_ABSTRACT,
|
|
140
|
+
filters: [{ field: "type", operator: "=", value: "article" }],
|
|
141
|
+
limit: 5,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const dsl = client.rawQuery.mock.calls[0][0] as string;
|
|
145
|
+
expect(dsl).toContain('type = "article"');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("escapes quotes in text", async () => {
|
|
149
|
+
client.rawQuery.mockResolvedValue(apiRows("publications", [], 0));
|
|
150
|
+
|
|
151
|
+
await callTool(handlers, "similar_documents", {
|
|
152
|
+
entityType: "publications",
|
|
153
|
+
text: 'Text with "quotes" inside',
|
|
154
|
+
limit: 5,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const dsl = client.rawQuery.mock.calls[0][0] as string;
|
|
158
|
+
expect(dsl).toContain('similar_documents("Text with \\"quotes\\" inside")');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("handles API errors gracefully", async () => {
|
|
162
|
+
client.rawQuery.mockRejectedValueOnce(new Error("Similarity search failed"));
|
|
163
|
+
|
|
164
|
+
const result = await callTool(handlers, "similar_documents", {
|
|
165
|
+
entityType: "publications",
|
|
166
|
+
text: SAMPLE_ABSTRACT,
|
|
167
|
+
});
|
|
168
|
+
const data = parseToolResult(result);
|
|
169
|
+
|
|
170
|
+
expect(result.isError).toBe(true);
|
|
171
|
+
expect(data.error).toBe("Similarity search failed");
|
|
172
|
+
});
|
|
173
|
+
});
|
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/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"],"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/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"}
|