@digital-science-dsl/dimensions-analytics-mcp 1.2.0 → 1.3.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 +14 -0
- package/README.md +1 -1
- 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 +3 -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 +19 -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 +2 -2
- 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 +1 -1
- package/dist/mcp/tools/similar-documents.d.ts.map +1 -1
- package/dist/mcp/tools/similar-documents.js +4 -9
- package/dist/mcp/tools/similar-documents.js.map +1 -1
- 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 +3 -1
- package/src/mcp/examples/usage-scenarios.ts +19 -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 +3 -3
- 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 +5 -6
- 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 +3 -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/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 +15 -3
- package/tsconfig.tsbuildinfo +1 -1
package/src/main.ts
CHANGED
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
* Starts the Dimensions Analytics MCP server with stdio transport.
|
|
5
5
|
* @module main
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
8
|
+
import { maybeAutoUpdateAndReexec } from "./mcp/auto-update.js";
|
|
9
9
|
import { createMcpServerAsync } from "./mcp/server.js";
|
|
10
10
|
|
|
11
|
+
maybeAutoUpdateAndReexec();
|
|
12
|
+
|
|
11
13
|
let close: () => Promise<void>;
|
|
12
14
|
|
|
13
15
|
try {
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort version check and prefix-only auto-update for stdio installs.
|
|
3
|
+
* @module mcp/auto-update
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { homedir, tmpdir } from "node:os";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
export const NPM_PACKAGE = "@digital-science-dsl/dimensions-analytics-mcp";
|
|
13
|
+
export const UPDATING_DOCS_URL =
|
|
14
|
+
"https://github.com/digital-science/dimensions-analytics-mcp/blob/main/docs/INSTALLATION.md#updating";
|
|
15
|
+
export const DEFAULT_UPDATE_INTERVAL_MS = 86_400_000;
|
|
16
|
+
export const NPM_VIEW_TIMEOUT_MS = 3_000;
|
|
17
|
+
export const NPM_INSTALL_TIMEOUT_MS = 12_000;
|
|
18
|
+
export const PREFIX_DIR_NAME = ".dimensions-analytics-mcp";
|
|
19
|
+
|
|
20
|
+
const REGISTRY = "https://registry.npmjs.org";
|
|
21
|
+
const INSTALL_SH = `bash -c "$(curl -fsSL https://raw.githubusercontent.com/digital-science/dimensions-analytics-mcp/main/scripts/install.sh)"`;
|
|
22
|
+
const INSTALL_PS1 =
|
|
23
|
+
"irm https://raw.githubusercontent.com/digital-science/dimensions-analytics-mcp/main/scripts/install.ps1 | iex";
|
|
24
|
+
|
|
25
|
+
export type InstallLayout = "prefix" | "npx" | "global" | "source" | "hosted";
|
|
26
|
+
export type VersionRelation =
|
|
27
|
+
| "same"
|
|
28
|
+
| "older"
|
|
29
|
+
| "newer-same-major"
|
|
30
|
+
| "newer-major"
|
|
31
|
+
| "incomparable";
|
|
32
|
+
export type UpdateCheckResult = "skipped" | "current" | "warned" | "updated";
|
|
33
|
+
|
|
34
|
+
export interface UpdateStamp {
|
|
35
|
+
readonly checkedAt: number;
|
|
36
|
+
readonly from?: string;
|
|
37
|
+
readonly to?: string;
|
|
38
|
+
readonly skippedReason?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AutoUpdateIo {
|
|
42
|
+
readonly now: () => number;
|
|
43
|
+
readonly env: NodeJS.ProcessEnv;
|
|
44
|
+
readonly layout: InstallLayout;
|
|
45
|
+
readonly currentVersion: string;
|
|
46
|
+
readonly allowInstall: boolean;
|
|
47
|
+
readonly stampPath: string;
|
|
48
|
+
readonly readStamp: (path: string) => UpdateStamp | undefined;
|
|
49
|
+
readonly writeStamp: (path: string, stamp: UpdateStamp) => void;
|
|
50
|
+
readonly npmView: () => string | undefined;
|
|
51
|
+
readonly npmInstall: (version: string) => boolean;
|
|
52
|
+
readonly log: (message: string) => void;
|
|
53
|
+
readonly reexec: () => void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function envFlagOff(value: string | undefined): boolean {
|
|
57
|
+
if (value == null) return false;
|
|
58
|
+
return ["0", "false", "off", "no"].includes(value.trim().toLowerCase());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function resolvePackageRoot(moduleUrl: string = import.meta.url): string {
|
|
62
|
+
return join(dirname(fileURLToPath(moduleUrl)), "..", "..");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function readCurrentVersion(packageRoot: string): string {
|
|
66
|
+
const raw = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")) as {
|
|
67
|
+
version?: unknown;
|
|
68
|
+
};
|
|
69
|
+
if (typeof raw.version !== "string" || raw.version.length === 0) {
|
|
70
|
+
throw new Error(`Missing version in ${join(packageRoot, "package.json")}`);
|
|
71
|
+
}
|
|
72
|
+
return raw.version;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function normalizePath(path: string): string {
|
|
76
|
+
return path.replace(/\\/g, "/");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function detectInstallLayout(
|
|
80
|
+
packageRoot: string,
|
|
81
|
+
homeDir: string,
|
|
82
|
+
): Exclude<InstallLayout, "hosted"> {
|
|
83
|
+
const root = normalizePath(packageRoot);
|
|
84
|
+
const prefixRoot = normalizePath(
|
|
85
|
+
join(
|
|
86
|
+
homeDir,
|
|
87
|
+
PREFIX_DIR_NAME,
|
|
88
|
+
"node_modules",
|
|
89
|
+
"@digital-science-dsl",
|
|
90
|
+
"dimensions-analytics-mcp",
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
if (root === prefixRoot || root.startsWith(`${prefixRoot}/`)) return "prefix";
|
|
94
|
+
if (root.includes("/_npx/") || root.includes("/_npx")) return "npx";
|
|
95
|
+
if (root.includes("/node_modules/@digital-science-dsl/dimensions-analytics-mcp")) return "global";
|
|
96
|
+
return "source";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function parseSemver(
|
|
100
|
+
version: string,
|
|
101
|
+
): { major: number; minor: number; patch: number } | undefined {
|
|
102
|
+
const match = version
|
|
103
|
+
.trim()
|
|
104
|
+
.replace(/^v/i, "")
|
|
105
|
+
.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
106
|
+
if (!match) return undefined;
|
|
107
|
+
return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function versionRelation(current: string, latest: string): VersionRelation {
|
|
111
|
+
const a = parseSemver(current);
|
|
112
|
+
const b = parseSemver(latest);
|
|
113
|
+
if (!a || !b) return "incomparable";
|
|
114
|
+
if (a.major === b.major && a.minor === b.minor && a.patch === b.patch) return "same";
|
|
115
|
+
const cmp =
|
|
116
|
+
a.major !== b.major
|
|
117
|
+
? a.major - b.major
|
|
118
|
+
: a.minor !== b.minor
|
|
119
|
+
? a.minor - b.minor
|
|
120
|
+
: a.patch - b.patch;
|
|
121
|
+
if (cmp > 0) return "older";
|
|
122
|
+
if (b.major > a.major) return "newer-major";
|
|
123
|
+
return "newer-same-major";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function upgradeGuidance(
|
|
127
|
+
layout: InstallLayout,
|
|
128
|
+
current: string,
|
|
129
|
+
latest: string,
|
|
130
|
+
options: { major?: boolean; installFailed?: boolean } = {},
|
|
131
|
+
): string {
|
|
132
|
+
const lines = [`Dimensions Analytics MCP ${current} is behind npm ${latest}.`];
|
|
133
|
+
if (options.major) {
|
|
134
|
+
lines.push("This is a new major version; auto-update will not apply it.");
|
|
135
|
+
}
|
|
136
|
+
if (options.installFailed) {
|
|
137
|
+
lines.push("Automatic update failed; install the new version manually.");
|
|
138
|
+
}
|
|
139
|
+
lines.push(upgradeRecipe(layout));
|
|
140
|
+
lines.push(`See ${UPDATING_DOCS_URL}`);
|
|
141
|
+
return lines.join("\n");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function upgradeRecipe(layout: InstallLayout): string {
|
|
145
|
+
switch (layout) {
|
|
146
|
+
case "prefix":
|
|
147
|
+
return [
|
|
148
|
+
"Update with the installer, then restart your AI app:",
|
|
149
|
+
` Mac/Linux: ${INSTALL_SH}`,
|
|
150
|
+
` Windows PowerShell: ${INSTALL_PS1}`,
|
|
151
|
+
` Or: npm install ${NPM_PACKAGE}@latest --prefix ~/${PREFIX_DIR_NAME}`,
|
|
152
|
+
].join("\n");
|
|
153
|
+
case "npx":
|
|
154
|
+
return [
|
|
155
|
+
`Set MCP args to ["-y", "${NPM_PACKAGE}@latest"] (or clear the npx cache) and restart.`,
|
|
156
|
+
"Without @latest, npx keeps the cached tarball.",
|
|
157
|
+
].join("\n");
|
|
158
|
+
case "global":
|
|
159
|
+
return `Run: npm install -g ${NPM_PACKAGE}@latest\nThen restart your AI app.`;
|
|
160
|
+
case "source":
|
|
161
|
+
return "Run: git pull && pnpm install && pnpm run build";
|
|
162
|
+
case "hosted":
|
|
163
|
+
return "Bump the deployed image / npm pin and redeploy. Do not run the stdio installer on the host.";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function resolveNpmBin(
|
|
168
|
+
execPath: string = process.execPath,
|
|
169
|
+
platform: NodeJS.Platform = process.platform,
|
|
170
|
+
exists: (path: string) => boolean = existsSync,
|
|
171
|
+
): string {
|
|
172
|
+
const dir = dirname(execPath);
|
|
173
|
+
const names = platform === "win32" ? ["npm.cmd", "npm.exe", "npm"] : ["npm"];
|
|
174
|
+
for (const name of names) {
|
|
175
|
+
const candidate = join(dir, name);
|
|
176
|
+
if (exists(candidate)) return candidate;
|
|
177
|
+
}
|
|
178
|
+
return platform === "win32" ? "npm.cmd" : "npm";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function stampPathFor(layout: InstallLayout, homeDir: string, tmpDir: string): string {
|
|
182
|
+
if (layout === "hosted") {
|
|
183
|
+
return join(tmpDir, "dimensions-analytics-mcp-auto-update.json");
|
|
184
|
+
}
|
|
185
|
+
return join(homeDir, PREFIX_DIR_NAME, ".auto-update.json");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function readStampFile(path: string): UpdateStamp | undefined {
|
|
189
|
+
try {
|
|
190
|
+
const raw = JSON.parse(readFileSync(path, "utf8")) as Partial<UpdateStamp>;
|
|
191
|
+
if (typeof raw.checkedAt !== "number") return undefined;
|
|
192
|
+
return {
|
|
193
|
+
checkedAt: raw.checkedAt,
|
|
194
|
+
...(typeof raw.from === "string" ? { from: raw.from } : {}),
|
|
195
|
+
...(typeof raw.to === "string" ? { to: raw.to } : {}),
|
|
196
|
+
...(typeof raw.skippedReason === "string" ? { skippedReason: raw.skippedReason } : {}),
|
|
197
|
+
};
|
|
198
|
+
} catch {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function writeStampFile(path: string, stamp: UpdateStamp): void {
|
|
204
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
205
|
+
writeFileSync(path, `${JSON.stringify(stamp)}\n`, "utf8");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function updateIntervalMs(env: NodeJS.ProcessEnv): number {
|
|
209
|
+
const raw = env.DIMENSIONS_MCP_AUTO_UPDATE_INTERVAL_MS;
|
|
210
|
+
if (raw == null || raw.trim() === "") return DEFAULT_UPDATE_INTERVAL_MS;
|
|
211
|
+
const parsed = Number(raw);
|
|
212
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_UPDATE_INTERVAL_MS;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function runUpdateCheck(io: AutoUpdateIo): UpdateCheckResult {
|
|
216
|
+
if (io.env.DIMENSIONS_MCP_UPDATED === "1") return "skipped";
|
|
217
|
+
if (envFlagOff(io.env.DIMENSIONS_MCP_UPDATE_CHECK)) return "skipped";
|
|
218
|
+
|
|
219
|
+
const stamp = io.readStamp(io.stampPath);
|
|
220
|
+
if (stamp && io.now() - stamp.checkedAt < updateIntervalMs(io.env)) {
|
|
221
|
+
return "skipped";
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const latest = io.npmView();
|
|
225
|
+
if (latest == null) {
|
|
226
|
+
io.writeStamp(io.stampPath, {
|
|
227
|
+
checkedAt: io.now(),
|
|
228
|
+
from: io.currentVersion,
|
|
229
|
+
skippedReason: "view-failed",
|
|
230
|
+
});
|
|
231
|
+
return "current";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const relation = versionRelation(io.currentVersion, latest);
|
|
235
|
+
if (relation === "same" || relation === "older" || relation === "incomparable") {
|
|
236
|
+
io.writeStamp(io.stampPath, { checkedAt: io.now(), from: io.currentVersion, to: latest });
|
|
237
|
+
return "current";
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const canInstall =
|
|
241
|
+
io.allowInstall &&
|
|
242
|
+
io.layout === "prefix" &&
|
|
243
|
+
!envFlagOff(io.env.DIMENSIONS_MCP_AUTO_UPDATE) &&
|
|
244
|
+
relation === "newer-same-major";
|
|
245
|
+
|
|
246
|
+
if (canInstall) {
|
|
247
|
+
const ok = io.npmInstall(latest);
|
|
248
|
+
if (ok) {
|
|
249
|
+
io.writeStamp(io.stampPath, { checkedAt: io.now(), from: io.currentVersion, to: latest });
|
|
250
|
+
io.reexec();
|
|
251
|
+
return "updated";
|
|
252
|
+
}
|
|
253
|
+
io.log(
|
|
254
|
+
upgradeGuidance(io.layout, io.currentVersion, latest, {
|
|
255
|
+
installFailed: true,
|
|
256
|
+
major: false,
|
|
257
|
+
}),
|
|
258
|
+
);
|
|
259
|
+
io.writeStamp(io.stampPath, {
|
|
260
|
+
checkedAt: io.now(),
|
|
261
|
+
from: io.currentVersion,
|
|
262
|
+
to: latest,
|
|
263
|
+
skippedReason: "install-failed",
|
|
264
|
+
});
|
|
265
|
+
return "warned";
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
io.log(
|
|
269
|
+
upgradeGuidance(io.layout, io.currentVersion, latest, {
|
|
270
|
+
major: relation === "newer-major",
|
|
271
|
+
}),
|
|
272
|
+
);
|
|
273
|
+
io.writeStamp(io.stampPath, {
|
|
274
|
+
checkedAt: io.now(),
|
|
275
|
+
from: io.currentVersion,
|
|
276
|
+
to: latest,
|
|
277
|
+
skippedReason: relation === "newer-major" ? "major" : "manual",
|
|
278
|
+
});
|
|
279
|
+
return "warned";
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function spawnNpm(
|
|
283
|
+
args: string[],
|
|
284
|
+
timeout: number,
|
|
285
|
+
): { status: number | null; stdout: string; stderr: string } {
|
|
286
|
+
const npmBin = resolveNpmBin();
|
|
287
|
+
const result = spawnSync(npmBin, args, {
|
|
288
|
+
encoding: "utf8",
|
|
289
|
+
timeout,
|
|
290
|
+
windowsHide: true,
|
|
291
|
+
shell: process.platform === "win32",
|
|
292
|
+
});
|
|
293
|
+
return {
|
|
294
|
+
status: result.status,
|
|
295
|
+
stdout: result.stdout ?? "",
|
|
296
|
+
stderr: result.stderr ?? "",
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function npmViewLatest(): string | undefined {
|
|
301
|
+
const result = spawnNpm(
|
|
302
|
+
["view", NPM_PACKAGE, "version", "--registry", REGISTRY],
|
|
303
|
+
NPM_VIEW_TIMEOUT_MS,
|
|
304
|
+
);
|
|
305
|
+
if (result.status !== 0) return undefined;
|
|
306
|
+
const version = result.stdout.trim().replace(/^"+|"+$/g, "");
|
|
307
|
+
return parseSemver(version) ? version : undefined;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function npmInstallVersion(version: string, prefix: string): boolean {
|
|
311
|
+
const result = spawnNpm(
|
|
312
|
+
[
|
|
313
|
+
"install",
|
|
314
|
+
`${NPM_PACKAGE}@${version}`,
|
|
315
|
+
"--prefix",
|
|
316
|
+
prefix,
|
|
317
|
+
"--registry",
|
|
318
|
+
REGISTRY,
|
|
319
|
+
"--no-fund",
|
|
320
|
+
"--no-audit",
|
|
321
|
+
],
|
|
322
|
+
NPM_INSTALL_TIMEOUT_MS,
|
|
323
|
+
);
|
|
324
|
+
return result.status === 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function reexecCurrentProcess(): void {
|
|
328
|
+
const result = spawnSync(process.execPath, process.argv.slice(1), {
|
|
329
|
+
env: { ...process.env, DIMENSIONS_MCP_UPDATED: "1" },
|
|
330
|
+
stdio: "inherit",
|
|
331
|
+
windowsHide: true,
|
|
332
|
+
});
|
|
333
|
+
process.exit(result.status ?? 1);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function createIo(options: { allowInstall: boolean; layout?: InstallLayout }): AutoUpdateIo {
|
|
337
|
+
const home = homedir();
|
|
338
|
+
const packageRoot = resolvePackageRoot();
|
|
339
|
+
const layout = options.layout ?? detectInstallLayout(packageRoot, home);
|
|
340
|
+
const prefix = join(home, PREFIX_DIR_NAME);
|
|
341
|
+
return {
|
|
342
|
+
now: () => Date.now(),
|
|
343
|
+
env: process.env,
|
|
344
|
+
layout,
|
|
345
|
+
currentVersion: readCurrentVersion(packageRoot),
|
|
346
|
+
allowInstall: options.allowInstall,
|
|
347
|
+
stampPath: stampPathFor(layout, home, tmpdir()),
|
|
348
|
+
readStamp: readStampFile,
|
|
349
|
+
writeStamp: writeStampFile,
|
|
350
|
+
npmView: npmViewLatest,
|
|
351
|
+
npmInstall: (version) => npmInstallVersion(version, prefix),
|
|
352
|
+
log: (message) => console.error(message),
|
|
353
|
+
reexec: reexecCurrentProcess,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Stdio entry: may install + re-exec for prefix layouts.
|
|
359
|
+
*/
|
|
360
|
+
export function maybeAutoUpdateAndReexec(): void {
|
|
361
|
+
try {
|
|
362
|
+
runUpdateCheck(createIo({ allowInstall: true }));
|
|
363
|
+
} catch (error: unknown) {
|
|
364
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
365
|
+
console.error(`Dimensions Analytics MCP update check failed: ${message}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Hosted HTTP entry: version warning only, never install or re-exec.
|
|
371
|
+
*/
|
|
372
|
+
export function maybeWarnStaleHostedVersion(): void {
|
|
373
|
+
try {
|
|
374
|
+
runUpdateCheck(createIo({ allowInstall: false, layout: "hosted" }));
|
|
375
|
+
} catch (error: unknown) {
|
|
376
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
377
|
+
console.error(`Dimensions Analytics MCP update check failed: ${message}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
@@ -55,13 +55,15 @@ export const DSL_EXAMPLES_BY_ENTITY: Record<string, readonly string[]> = {
|
|
|
55
55
|
],
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
/** General DSL patterns (describe, functions, pagination). */
|
|
58
|
+
/** General DSL patterns (describe, functions, pagination, similarity). */
|
|
59
59
|
export const DSL_GENERAL_EXAMPLES = [
|
|
60
60
|
"describe version",
|
|
61
61
|
"describe schema",
|
|
62
62
|
"describe source publications",
|
|
63
63
|
'extract_concepts(text: "CRISPR gene editing enables precise DNA modification")',
|
|
64
64
|
'classify(text: "Stem cell therapy for diabetes", system: "for")',
|
|
65
|
+
'search publications for similar_documents("After spinal cord injury, macrophages infiltrate the lesion site") where year > 2015 return publications[id+title+year] sort by score desc limit 5',
|
|
66
|
+
'search grants for similar_documents("Cancer immunotherapy with checkpoint inhibitors and CAR-T") where start_year >= 2020 return grants[id+title] sort by score desc limit 5',
|
|
65
67
|
] as const;
|
|
66
68
|
|
|
67
69
|
/** Link to official DSL documentation. */
|
|
@@ -183,6 +183,25 @@ export const USAGE_SCENARIOS: readonly UsageScenario[] = [
|
|
|
183
183
|
"limit 10",
|
|
184
184
|
],
|
|
185
185
|
},
|
|
186
|
+
{
|
|
187
|
+
id: "similar-grants",
|
|
188
|
+
goal: "Similar grants from a description",
|
|
189
|
+
prompt: "Find grants similar to this description of CAR-T cancer immunotherapy.",
|
|
190
|
+
tool: "similar_documents",
|
|
191
|
+
args: {
|
|
192
|
+
entityType: "grants",
|
|
193
|
+
text: "Development of novel cancer immunotherapy approaches using checkpoint inhibitors and CAR-T cell engineering for solid tumors.",
|
|
194
|
+
yearFrom: 2020,
|
|
195
|
+
limit: 10,
|
|
196
|
+
fields: ["id", "title", "funding_usd", "start_year"],
|
|
197
|
+
},
|
|
198
|
+
dslContains: [
|
|
199
|
+
"search grants for similar_documents(",
|
|
200
|
+
"start_year >= 2020",
|
|
201
|
+
"sort by score desc",
|
|
202
|
+
"limit 10",
|
|
203
|
+
],
|
|
204
|
+
},
|
|
186
205
|
{
|
|
187
206
|
id: "build-complex-query",
|
|
188
207
|
goal: "Build a complex query",
|
package/src/mcp/http-server.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import type { IncomingMessage, Server } from "node:http";
|
|
8
|
-
import { createMcpExpressApp } from "@modelcontextprotocol/
|
|
9
|
-
import {
|
|
8
|
+
import { createMcpExpressApp } from "@modelcontextprotocol/express";
|
|
9
|
+
import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
|
|
10
10
|
import type { Request, Response } from "express";
|
|
11
11
|
import type { HostedEnvConfig } from "../client/deployment-config.js";
|
|
12
12
|
import { AuthenticationError } from "../client/errors.js";
|
|
@@ -73,7 +73,7 @@ export function startHostedHttpServer(options: HostedHttpServerOptions): HostedH
|
|
|
73
73
|
mcpClient,
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
const transport = new
|
|
76
|
+
const transport = new NodeStreamableHTTPServerTransport({
|
|
77
77
|
sessionIdGenerator: undefined,
|
|
78
78
|
});
|
|
79
79
|
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
* via a higher-order function (decorator) pattern.
|
|
5
5
|
* @module mcp/middleware/field-aliases
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import type { CallToolResult } from "@modelcontextprotocol/server";
|
|
9
8
|
import type { EntityType } from "../../dsl/index.js";
|
|
10
9
|
|
|
11
10
|
// ---------------------------------------------------------------------------
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* @module mcp/resources/schema
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { McpServer } from "@modelcontextprotocol/
|
|
7
|
-
import { ResourceTemplate } from "@modelcontextprotocol/
|
|
6
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
7
|
+
import { ResourceTemplate } from "@modelcontextprotocol/server";
|
|
8
8
|
import {
|
|
9
9
|
buildUsagePolicy,
|
|
10
10
|
type DescribeField,
|
|
@@ -32,7 +32,7 @@ import type { SchemaContext } from "../schema/context.js";
|
|
|
32
32
|
*/
|
|
33
33
|
export function registerSchemaResources(server: McpServer, context: SchemaContext): void {
|
|
34
34
|
const schemaStore = () => context.store;
|
|
35
|
-
server.
|
|
35
|
+
server.registerResource(
|
|
36
36
|
"schema-summary",
|
|
37
37
|
"dimensions://schema/summary",
|
|
38
38
|
{
|
|
@@ -52,7 +52,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
|
|
|
52
52
|
);
|
|
53
53
|
|
|
54
54
|
// Full schema
|
|
55
|
-
server.
|
|
55
|
+
server.registerResource(
|
|
56
56
|
"schema-full",
|
|
57
57
|
"dimensions://schema",
|
|
58
58
|
{
|
|
@@ -72,7 +72,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
|
|
|
72
72
|
);
|
|
73
73
|
|
|
74
74
|
// Version
|
|
75
|
-
server.
|
|
75
|
+
server.registerResource(
|
|
76
76
|
"schema-version",
|
|
77
77
|
"dimensions://schema/version",
|
|
78
78
|
{
|
|
@@ -95,7 +95,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
|
|
|
95
95
|
);
|
|
96
96
|
|
|
97
97
|
// Limits (static until upstream `describe limits` exists)
|
|
98
|
-
server.
|
|
98
|
+
server.registerResource(
|
|
99
99
|
"schema-limits",
|
|
100
100
|
"dimensions://schema/limits",
|
|
101
101
|
{
|
|
@@ -113,7 +113,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
|
|
|
113
113
|
}),
|
|
114
114
|
);
|
|
115
115
|
|
|
116
|
-
server.
|
|
116
|
+
server.registerResource(
|
|
117
117
|
"schema-policy",
|
|
118
118
|
"dimensions://schema/policy",
|
|
119
119
|
{
|
|
@@ -132,7 +132,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
|
|
|
132
132
|
}),
|
|
133
133
|
);
|
|
134
134
|
|
|
135
|
-
server.
|
|
135
|
+
server.registerResource(
|
|
136
136
|
"dsl-examples",
|
|
137
137
|
"dimensions://examples",
|
|
138
138
|
{
|
|
@@ -160,7 +160,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
|
|
|
160
160
|
}),
|
|
161
161
|
);
|
|
162
162
|
|
|
163
|
-
server.
|
|
163
|
+
server.registerResource(
|
|
164
164
|
"dsl-examples-by-source",
|
|
165
165
|
new ResourceTemplate("dimensions://examples/{source}", {
|
|
166
166
|
list: async () => ({
|
|
@@ -219,7 +219,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
|
|
|
219
219
|
);
|
|
220
220
|
|
|
221
221
|
const sourceNames = () => schemaStore().sourceNames();
|
|
222
|
-
server.
|
|
222
|
+
server.registerResource(
|
|
223
223
|
"schema-sources",
|
|
224
224
|
new ResourceTemplate("dimensions://schema/sources/{sourceName}", {
|
|
225
225
|
list: async () => ({
|
|
@@ -256,7 +256,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
|
|
|
256
256
|
);
|
|
257
257
|
|
|
258
258
|
const entityNames = () => schemaStore().entityNames();
|
|
259
|
-
server.
|
|
259
|
+
server.registerResource(
|
|
260
260
|
"schema-entities",
|
|
261
261
|
new ResourceTemplate("dimensions://schema/entities/{entityName}", {
|
|
262
262
|
list: async () => ({
|
|
@@ -294,7 +294,7 @@ export function registerSchemaResources(server: McpServer, context: SchemaContex
|
|
|
294
294
|
|
|
295
295
|
// Compatibility view for structured search tools
|
|
296
296
|
const entityTypes = () => schemaStore().structuredEntityTypes();
|
|
297
|
-
server.
|
|
297
|
+
server.registerResource(
|
|
298
298
|
"fields",
|
|
299
299
|
new ResourceTemplate("dimensions://fields/{entityType}", {
|
|
300
300
|
list: async () => ({
|
package/src/mcp/server.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* @module mcp/server
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
7
|
+
import type { Transport } from "@modelcontextprotocol/server";
|
|
8
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
9
|
+
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
10
10
|
import pkg from "../../package.json" with { type: "json" };
|
|
11
11
|
import { type HostedEnvConfig, loadDeploymentConfig } from "../client/index.js";
|
|
12
12
|
import { createBootstrapDimensionsClient, createDimensionsClient } from "../dsl/create-client.js";
|