@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.
- package/CHANGELOG.md +14 -0
- package/dist/http-main.js +2 -0
- package/dist/http-main.js.map +1 -1
- package/dist/main.d.ts +0 -5
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +3 -1
- package/dist/main.js.map +1 -1
- package/dist/mcp/auto-update.d.ts +63 -0
- package/dist/mcp/auto-update.d.ts.map +1 -0
- package/dist/mcp/auto-update.js +295 -0
- package/dist/mcp/auto-update.js.map +1 -0
- package/dist/mcp/examples/dsl-examples.d.ts +2 -2
- package/dist/mcp/examples/dsl-examples.d.ts.map +1 -1
- package/dist/mcp/examples/dsl-examples.js +5 -1
- package/dist/mcp/examples/dsl-examples.js.map +1 -1
- package/dist/mcp/examples/usage-scenarios.d.ts.map +1 -1
- package/dist/mcp/examples/usage-scenarios.js +38 -0
- package/dist/mcp/examples/usage-scenarios.js.map +1 -1
- package/dist/mcp/http-server.js +3 -3
- package/dist/mcp/http-server.js.map +1 -1
- package/dist/mcp/middleware/field-aliases.d.ts +1 -1
- package/dist/mcp/middleware/field-aliases.d.ts.map +1 -1
- package/dist/mcp/middleware/field-aliases.js +0 -6
- package/dist/mcp/middleware/field-aliases.js.map +1 -1
- package/dist/mcp/resources/schema.d.ts +1 -1
- package/dist/mcp/resources/schema.d.ts.map +1 -1
- package/dist/mcp/resources/schema.js +11 -11
- package/dist/mcp/resources/schema.js.map +1 -1
- package/dist/mcp/server.d.ts +2 -2
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +8 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/analytics.d.ts +1 -1
- package/dist/mcp/tools/analytics.d.ts.map +1 -1
- package/dist/mcp/tools/analytics.js +21 -21
- package/dist/mcp/tools/analytics.js.map +1 -1
- package/dist/mcp/tools/fetch-search-pages.d.ts +1 -1
- package/dist/mcp/tools/fetch-search-pages.d.ts.map +1 -1
- package/dist/mcp/tools/fetch-search-pages.js +4 -9
- package/dist/mcp/tools/fetch-search-pages.js.map +1 -1
- package/dist/mcp/tools/functions.d.ts +1 -1
- package/dist/mcp/tools/functions.d.ts.map +1 -1
- package/dist/mcp/tools/functions.js +8 -13
- package/dist/mcp/tools/functions.js.map +1 -1
- package/dist/mcp/tools/lookup.d.ts +1 -1
- package/dist/mcp/tools/lookup.d.ts.map +1 -1
- package/dist/mcp/tools/lookup.js +12 -17
- package/dist/mcp/tools/lookup.js.map +1 -1
- package/dist/mcp/tools/query.d.ts +1 -1
- package/dist/mcp/tools/query.d.ts.map +1 -1
- package/dist/mcp/tools/query.js +4 -9
- package/dist/mcp/tools/query.js.map +1 -1
- package/dist/mcp/tools/schema.d.ts +1 -1
- package/dist/mcp/tools/schema.d.ts.map +1 -1
- package/dist/mcp/tools/schema.js +7 -11
- package/dist/mcp/tools/schema.js.map +1 -1
- package/dist/mcp/tools/search.d.ts +1 -1
- package/dist/mcp/tools/search.d.ts.map +1 -1
- package/dist/mcp/tools/search.js +4 -9
- package/dist/mcp/tools/search.js.map +1 -1
- package/dist/mcp/tools/similar-documents.d.ts +26 -0
- package/dist/mcp/tools/similar-documents.d.ts.map +1 -0
- package/dist/mcp/tools/similar-documents.js +166 -0
- package/dist/mcp/tools/similar-documents.js.map +1 -0
- package/dist/mcp/usage-tracking.d.ts +1 -1
- package/dist/mcp/usage-tracking.d.ts.map +1 -1
- package/dist/mcp/utils.d.ts +1 -2
- package/dist/mcp/utils.d.ts.map +1 -1
- package/dist/mcp/utils.js +0 -4
- package/dist/mcp/utils.js.map +1 -1
- package/package.json +6 -2
- package/src/http-main.ts +3 -0
- package/src/main.ts +4 -2
- package/src/mcp/auto-update.ts +379 -0
- package/src/mcp/examples/dsl-examples.ts +5 -1
- package/src/mcp/examples/usage-scenarios.ts +38 -0
- package/src/mcp/http-server.ts +3 -3
- package/src/mcp/middleware/field-aliases.ts +1 -2
- package/src/mcp/resources/schema.ts +12 -12
- package/src/mcp/server.ts +9 -5
- package/src/mcp/tools/analytics.ts +26 -26
- package/src/mcp/tools/fetch-search-pages.ts +5 -6
- package/src/mcp/tools/functions.ts +9 -10
- package/src/mcp/tools/lookup.ts +13 -14
- package/src/mcp/tools/query.ts +5 -6
- package/src/mcp/tools/schema.ts +8 -9
- package/src/mcp/tools/search.ts +5 -6
- package/src/mcp/tools/similar-documents.ts +231 -0
- package/src/mcp/usage-tracking.ts +1 -1
- package/src/mcp/utils.ts +1 -3
- package/test/examples/dsl-examples.test.ts +8 -0
- package/test/examples/usage-scenarios.test.ts +5 -3
- package/test/integration/harness.ts +1 -3
- package/test/integration/hosted-client.ts +1 -3
- package/test/integration/hosted.e2e.test.ts +1 -1
- package/test/integration/suites/index.ts +2 -0
- package/test/integration/suites/similar-documents.integration.ts +51 -0
- package/test/integration/verify-usage-scenarios.ts +1 -3
- package/test/mcp/auto-update.test.ts +347 -0
- package/test/middleware/field-aliases.test.ts +1 -1
- package/test/resources/schema.test.ts +5 -1
- package/test/server.test.ts +17 -3
- package/test/tools/similar-documents.test.ts +173 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/mcp/tools/schema.ts
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* Schema introspection and validation tools for the MCP server.
|
|
3
3
|
* @module mcp/tools/schema
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
7
6
|
import { z } from "zod";
|
|
8
7
|
import type { DimensionsClient } from "../../dsl/index.js";
|
|
9
8
|
import { buildReverseAliasMap } from "../middleware/field-aliases.js";
|
|
@@ -32,7 +31,7 @@ export function registerSchemaTools(
|
|
|
32
31
|
"Default: compact summary with source/entity counts and resource URIs. " +
|
|
33
32
|
"Use full=true or dimensions://schema for the complete describe payload. " +
|
|
34
33
|
"Optionally fetch live describe for a single source or entity.",
|
|
35
|
-
inputSchema: {
|
|
34
|
+
inputSchema: z.object({
|
|
36
35
|
source: z.string().optional().describe("Source name (e.g. publications)"),
|
|
37
36
|
entity: z.string().optional().describe("Auxiliary entity name (e.g. journals)"),
|
|
38
37
|
full: z
|
|
@@ -43,8 +42,8 @@ export function registerSchemaTools(
|
|
|
43
42
|
.boolean()
|
|
44
43
|
.optional()
|
|
45
44
|
.describe("When true, fetch fresh describe for source/entity from the API"),
|
|
46
|
-
},
|
|
47
|
-
outputSchema: {
|
|
45
|
+
}),
|
|
46
|
+
outputSchema: z.object({
|
|
48
47
|
summary: z
|
|
49
48
|
.record(z.string(), z.unknown())
|
|
50
49
|
.optional()
|
|
@@ -55,7 +54,7 @@ export function registerSchemaTools(
|
|
|
55
54
|
.describe("Full or sliced schema when full=true or source/entity is set"),
|
|
56
55
|
version: z.string().optional().describe("DSL version"),
|
|
57
56
|
stats: z.record(z.string(), z.unknown()).optional().describe("Load stats and provenance"),
|
|
58
|
-
},
|
|
57
|
+
}),
|
|
59
58
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
60
59
|
},
|
|
61
60
|
async (args) => {
|
|
@@ -106,11 +105,11 @@ export function registerSchemaTools(
|
|
|
106
105
|
{
|
|
107
106
|
description:
|
|
108
107
|
"Re-fetch describe schema from the Dimensions API, rebuild in-memory schema, and re-register MCP resources.",
|
|
109
|
-
inputSchema: {},
|
|
110
|
-
outputSchema: {
|
|
108
|
+
inputSchema: z.object({}),
|
|
109
|
+
outputSchema: z.object({
|
|
111
110
|
refreshed: z.boolean(),
|
|
112
111
|
stats: z.record(z.string(), z.unknown()),
|
|
113
|
-
},
|
|
112
|
+
}),
|
|
114
113
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
115
114
|
},
|
|
116
115
|
async () => {
|
package/src/mcp/tools/search.ts
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* Registers `search_{source}` tools from describe schema and shared QueryBuilder handlers.
|
|
4
4
|
* @module mcp/tools/search
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
8
7
|
import { z } from "zod";
|
|
9
8
|
import type { DimensionsClient } from "../../dsl/index.js";
|
|
10
9
|
import {
|
|
@@ -105,11 +104,11 @@ export function registerSearchTools(
|
|
|
105
104
|
toolName,
|
|
106
105
|
{
|
|
107
106
|
description: meta.description,
|
|
108
|
-
inputSchema: {
|
|
107
|
+
inputSchema: z.object({
|
|
109
108
|
...SHARED_SEARCH_INPUT,
|
|
110
109
|
...meta.extraInputSchema,
|
|
111
|
-
},
|
|
112
|
-
outputSchema: {
|
|
110
|
+
}),
|
|
111
|
+
outputSchema: z.object({
|
|
113
112
|
totalCount: z.number().describe("Total matching records"),
|
|
114
113
|
returnedCount: z.number().describe("Records returned in this response"),
|
|
115
114
|
truncated: z
|
|
@@ -119,7 +118,7 @@ export function registerSearchTools(
|
|
|
119
118
|
truncationWarning: z.string().optional().describe("Warning message when truncated"),
|
|
120
119
|
...PAGINATION_OUTPUT_SCHEMA,
|
|
121
120
|
[resultKey]: z.array(z.record(z.string(), z.unknown())).describe("Result records"),
|
|
122
|
-
},
|
|
121
|
+
}),
|
|
123
122
|
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
124
123
|
},
|
|
125
124
|
withFieldAliases(
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* similar_documents MCP tool — find publications/grants with similar topics via DSL.
|
|
3
|
+
* Uses concept extraction + weighted concepts search (not vector embeddings).
|
|
4
|
+
* @module mcp/tools/similar-documents
|
|
5
|
+
*/
|
|
6
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import type { DimensionsClient } from "../../dsl/index.js";
|
|
9
|
+
import {
|
|
10
|
+
applyFilters,
|
|
11
|
+
type ExtendedWhereFilterInput,
|
|
12
|
+
ExtendedWhereFilterSchema,
|
|
13
|
+
parseEntityResponse,
|
|
14
|
+
resolveSkipAndLimit,
|
|
15
|
+
SCHEMA_LIMITS,
|
|
16
|
+
type StructuredEntityType,
|
|
17
|
+
searchResultKey,
|
|
18
|
+
validateSearchPaginationPolicy,
|
|
19
|
+
} from "../../dsl/index.js";
|
|
20
|
+
import { withFieldAliases } from "../middleware/field-aliases.js";
|
|
21
|
+
import { registerTrackedTool } from "../usage-tracking.js";
|
|
22
|
+
import {
|
|
23
|
+
formatErrorResult,
|
|
24
|
+
formatToolResult,
|
|
25
|
+
READ_ONLY_API_ANNOTATIONS,
|
|
26
|
+
withSearchPagination,
|
|
27
|
+
} from "../utils.js";
|
|
28
|
+
import { PAGINATION_INPUT_SCHEMA, PAGINATION_OUTPUT_SCHEMA } from "./search-input.js";
|
|
29
|
+
|
|
30
|
+
/** Entity types that support the DSL similar_documents() search function. */
|
|
31
|
+
export const SIMILAR_DOCUMENTS_ENTITY_TYPES = ["publications", "grants"] as const;
|
|
32
|
+
|
|
33
|
+
/** Entity type supported by similar_documents. */
|
|
34
|
+
export type SimilarDocumentsEntityType = (typeof SIMILAR_DOCUMENTS_ENTITY_TYPES)[number];
|
|
35
|
+
|
|
36
|
+
/** Year filter field per entity (publications use year; grants use start_year). */
|
|
37
|
+
const YEAR_FIELD: Record<SimilarDocumentsEntityType, string> = {
|
|
38
|
+
publications: "year",
|
|
39
|
+
grants: "start_year",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Builds DSL for a similar_documents search from tool arguments.
|
|
44
|
+
* @param client - Dimensions client
|
|
45
|
+
* @param entityType - publications or grants
|
|
46
|
+
* @param args - Tool arguments (after field-alias resolution)
|
|
47
|
+
* @returns DSL query string
|
|
48
|
+
*/
|
|
49
|
+
export function buildSimilarDocumentsDsl(
|
|
50
|
+
client: DimensionsClient,
|
|
51
|
+
entityType: SimilarDocumentsEntityType,
|
|
52
|
+
args: Record<string, unknown>,
|
|
53
|
+
): string {
|
|
54
|
+
const text = String(args.text ?? "");
|
|
55
|
+
const builder = client.createQueryBuilder().search(entityType).forSimilar(text);
|
|
56
|
+
|
|
57
|
+
const yearField = YEAR_FIELD[entityType];
|
|
58
|
+
if (typeof args.yearFrom === "number") {
|
|
59
|
+
builder.where(yearField, ">=", args.yearFrom);
|
|
60
|
+
}
|
|
61
|
+
if (typeof args.yearTo === "number") {
|
|
62
|
+
builder.where(yearField, "<=", args.yearTo);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const filters = args.filters as ExtendedWhereFilterInput[] | undefined;
|
|
66
|
+
if (filters?.length) {
|
|
67
|
+
applyFilters(builder, filters);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const fields = args.fields as string[] | undefined;
|
|
71
|
+
if (fields?.length) {
|
|
72
|
+
builder.fields(fields);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const sortBy = typeof args.sortBy === "string" ? args.sortBy : "score";
|
|
76
|
+
builder.sort(sortBy, "desc");
|
|
77
|
+
|
|
78
|
+
// Default to 20 for similarity (top-N relevance), not the search_* default of 100.
|
|
79
|
+
const { skip, limit } = resolveSkipAndLimit({
|
|
80
|
+
skip: args.skip as number | undefined,
|
|
81
|
+
page: args.page as number | undefined,
|
|
82
|
+
limit: (args.limit as number | undefined) ?? 20,
|
|
83
|
+
pageSize: args.pageSize as number | undefined,
|
|
84
|
+
});
|
|
85
|
+
builder.limit(limit);
|
|
86
|
+
if (skip > 0) {
|
|
87
|
+
builder.skip(skip);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return builder.build();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Registers the similar_documents tool with the MCP server.
|
|
95
|
+
* @param server - MCP server instance
|
|
96
|
+
* @param client - Dimensions client instance
|
|
97
|
+
*/
|
|
98
|
+
export function registerSimilarDocumentsTool(server: McpServer, client: DimensionsClient): void {
|
|
99
|
+
registerTrackedTool(
|
|
100
|
+
server,
|
|
101
|
+
"similar_documents",
|
|
102
|
+
{
|
|
103
|
+
description:
|
|
104
|
+
"Find publications or grants with similar research topics based on abstract/description text. " +
|
|
105
|
+
"Uses Dimensions concept extraction and weighted concepts search (not vector embeddings). " +
|
|
106
|
+
"Prefer this over execute_dsl for similarity. For a known record: get_by_id / get_by_doi first, " +
|
|
107
|
+
"then pass its abstract/description as text. Supported entityType: publications, grants only.",
|
|
108
|
+
inputSchema: z.object({
|
|
109
|
+
entityType: z
|
|
110
|
+
.enum(SIMILAR_DOCUMENTS_ENTITY_TYPES)
|
|
111
|
+
.describe("Entity type to search (publications or grants)"),
|
|
112
|
+
text: z
|
|
113
|
+
.string()
|
|
114
|
+
.min(1)
|
|
115
|
+
.describe(
|
|
116
|
+
"Abstract, description, or other prose to find similar documents for. " +
|
|
117
|
+
"Longer topical text works better than short keywords.",
|
|
118
|
+
),
|
|
119
|
+
limit: z
|
|
120
|
+
.number()
|
|
121
|
+
.int()
|
|
122
|
+
.min(1)
|
|
123
|
+
.max(SCHEMA_LIMITS.maxLimit)
|
|
124
|
+
.default(20)
|
|
125
|
+
.describe(`Maximum results to return (max ${SCHEMA_LIMITS.maxLimit}, default 20)`),
|
|
126
|
+
...PAGINATION_INPUT_SCHEMA,
|
|
127
|
+
fields: z
|
|
128
|
+
.array(z.string())
|
|
129
|
+
.optional()
|
|
130
|
+
.describe(
|
|
131
|
+
"Fields to return. Accepts aliases or DSL names. Use dimensions://fields/{entity} for the full list.",
|
|
132
|
+
),
|
|
133
|
+
filters: z
|
|
134
|
+
.array(ExtendedWhereFilterSchema)
|
|
135
|
+
.optional()
|
|
136
|
+
.describe("Additional where-clause filters"),
|
|
137
|
+
yearFrom: z
|
|
138
|
+
.number()
|
|
139
|
+
.int()
|
|
140
|
+
.optional()
|
|
141
|
+
.describe("Filter from this year inclusive (publications: year; grants: start_year)"),
|
|
142
|
+
yearTo: z
|
|
143
|
+
.number()
|
|
144
|
+
.int()
|
|
145
|
+
.optional()
|
|
146
|
+
.describe("Filter up to this year inclusive (publications: year; grants: start_year)"),
|
|
147
|
+
sortBy: z
|
|
148
|
+
.string()
|
|
149
|
+
.optional()
|
|
150
|
+
.describe(
|
|
151
|
+
"Sort field (default: score for relevance). Examples: score, times_cited, year, start_year.",
|
|
152
|
+
),
|
|
153
|
+
confirmLargeFetch: z
|
|
154
|
+
.boolean()
|
|
155
|
+
.optional()
|
|
156
|
+
.default(false)
|
|
157
|
+
.describe(
|
|
158
|
+
"Required when skip≥5000, page≥5, or limit=1000 with skip>0. See dimensions://schema/policy.",
|
|
159
|
+
),
|
|
160
|
+
}),
|
|
161
|
+
outputSchema: z.object({
|
|
162
|
+
entityType: z.enum(SIMILAR_DOCUMENTS_ENTITY_TYPES).describe("Entity type searched"),
|
|
163
|
+
totalCount: z.number().describe("Total matching records"),
|
|
164
|
+
returnedCount: z.number().describe("Records returned in this response"),
|
|
165
|
+
truncated: z.boolean().optional().describe("True when more results exist beyond this page"),
|
|
166
|
+
truncationWarning: z.string().optional().describe("Warning message when truncated"),
|
|
167
|
+
...PAGINATION_OUTPUT_SCHEMA,
|
|
168
|
+
publications: z
|
|
169
|
+
.array(z.record(z.string(), z.unknown()))
|
|
170
|
+
.optional()
|
|
171
|
+
.describe("Matching publications (when entityType is publications)"),
|
|
172
|
+
grants: z
|
|
173
|
+
.array(z.record(z.string(), z.unknown()))
|
|
174
|
+
.optional()
|
|
175
|
+
.describe("Matching grants (when entityType is grants)"),
|
|
176
|
+
}),
|
|
177
|
+
annotations: READ_ONLY_API_ANNOTATIONS,
|
|
178
|
+
},
|
|
179
|
+
withFieldAliases(
|
|
180
|
+
{
|
|
181
|
+
entitySource: { kind: "dynamic", argName: "entityType" },
|
|
182
|
+
fieldArrayArgs: ["fields"],
|
|
183
|
+
fieldStringArgs: ["sortBy"],
|
|
184
|
+
filterArrayArgs: ["filters"],
|
|
185
|
+
},
|
|
186
|
+
async (args) => {
|
|
187
|
+
try {
|
|
188
|
+
const entityType = args.entityType as SimilarDocumentsEntityType;
|
|
189
|
+
const record = args as Record<string, unknown>;
|
|
190
|
+
const { skip, limit } = resolveSkipAndLimit({
|
|
191
|
+
skip: record.skip as number | undefined,
|
|
192
|
+
page: record.page as number | undefined,
|
|
193
|
+
limit: (record.limit as number | undefined) ?? 20,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
validateSearchPaginationPolicy({
|
|
197
|
+
skip,
|
|
198
|
+
limit,
|
|
199
|
+
confirmLargeFetch: record.confirmLargeFetch as boolean | undefined,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const dsl = buildSimilarDocumentsDsl(client, entityType, {
|
|
203
|
+
...record,
|
|
204
|
+
limit,
|
|
205
|
+
});
|
|
206
|
+
const response = (await client.rawQuery(dsl)) as Record<string, unknown>;
|
|
207
|
+
const parsed = parseEntityResponse(response, entityType as StructuredEntityType);
|
|
208
|
+
const rows = parsed.data as Record<string, unknown>[];
|
|
209
|
+
const resultKey = searchResultKey(entityType as StructuredEntityType);
|
|
210
|
+
|
|
211
|
+
return formatToolResult(
|
|
212
|
+
withSearchPagination(
|
|
213
|
+
{
|
|
214
|
+
entityType,
|
|
215
|
+
totalCount: parsed.totalCount,
|
|
216
|
+
returnedCount: rows.length,
|
|
217
|
+
[resultKey]: rows,
|
|
218
|
+
},
|
|
219
|
+
parsed.totalCount,
|
|
220
|
+
rows.length,
|
|
221
|
+
skip,
|
|
222
|
+
limit,
|
|
223
|
+
),
|
|
224
|
+
);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
return formatErrorResult(error);
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
|
-
import type { McpServer } from "@modelcontextprotocol/
|
|
7
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
8
8
|
import pkg from "../../package.json" with { type: "json" };
|
|
9
9
|
import {
|
|
10
10
|
type McpDeploymentMode,
|
package/src/mcp/utils.ts
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
* Utility functions for MCP tools.
|
|
3
3
|
* @module mcp/utils
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
-
import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import type { McpServer, ToolAnnotations } from "@modelcontextprotocol/server";
|
|
8
6
|
import {
|
|
9
7
|
buildPaginationMetadata,
|
|
10
8
|
DimensionsError,
|
|
@@ -20,6 +20,14 @@ describe("dsl-examples", () => {
|
|
|
20
20
|
expect(getDslExamplesForSource("unknown")).toBeUndefined();
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
+
it("includes similar_documents examples for publications and grants", () => {
|
|
24
|
+
const pubs = getDslExamplesForSource("publications") ?? [];
|
|
25
|
+
const grants = getDslExamplesForSource("grants") ?? [];
|
|
26
|
+
expect(pubs.some((q) => q.includes("similar_documents("))).toBe(true);
|
|
27
|
+
expect(grants.some((q) => q.includes("similar_documents("))).toBe(true);
|
|
28
|
+
expect(DSL_GENERAL_EXAMPLES.some((q) => q.includes("similar_documents("))).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
23
31
|
it("uses sort by (not bare sort) and places sort before limit", () => {
|
|
24
32
|
for (const dsl of ALL_EXAMPLES) {
|
|
25
33
|
expect(dsl).not.toMatch(/\blimit \d+ sort\b/);
|
|
@@ -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 thirteen natural-language workflows", () => {
|
|
42
|
+
expect(USAGE_SCENARIOS).toHaveLength(13);
|
|
43
|
+
expect(new Set(USAGE_SCENARIOS.map((s) => s.id)).size).toBe(13);
|
|
42
44
|
});
|
|
43
45
|
|
|
44
46
|
for (const scenario of USAGE_SCENARIOS) {
|
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
* via in-memory transport, and executes eval suites against it.
|
|
6
6
|
* @module test/integration/harness
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
10
|
-
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
8
|
+
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
11
9
|
import { createMcpServerAsync, type McpServerConfig } from "../../src/mcp/server.js";
|
|
12
10
|
import { missingEnvVars } from "./env.js";
|
|
13
11
|
import type {
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
* MCP client helpers for hosted Streamable HTTP transport.
|
|
3
3
|
* @module test/integration/hosted-client
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
5
|
+
import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
8
6
|
import { hostedMcpEndpoint } from "./env.js";
|
|
9
7
|
|
|
10
8
|
export interface HostedMcpSession {
|
|
@@ -31,7 +31,7 @@ const MOCK_HOSTED: HostedEnvConfig = {
|
|
|
31
31
|
|
|
32
32
|
const TEST_API_KEY = "test-hosted-api-key";
|
|
33
33
|
|
|
34
|
-
describe("Hosted MCP e2e (mocked backends)", () => {
|
|
34
|
+
describe("Hosted MCP e2e (mocked backends)", { timeout: 30_000 }, () => {
|
|
35
35
|
let restoreFetch: (() => void) | undefined;
|
|
36
36
|
let server: HostedHttpServerHandle | undefined;
|
|
37
37
|
let baseUrl: string;
|
|
@@ -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
|
];
|
|
@@ -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
|
+
};
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
* Live verification of docs/USAGE.md natural-language scenarios.
|
|
4
4
|
* Run: node --import tsx apps/mcp/test/integration/verify-usage-scenarios.ts
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8
|
-
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
6
|
+
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
9
7
|
import { USAGE_SCENARIOS } from "../../src/mcp/examples/usage-scenarios.js";
|
|
10
8
|
import { buildServerInstructions, createMcpServerAsync } from "../../src/mcp/server.js";
|
|
11
9
|
|