@agimon-ai/doompi-core 0.0.1-alpha.87 → 0.0.1-alpha.88
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/dist/headlessSessionHost.d.mts +2 -0
- package/dist/headlessSessionHost.d.mts.map +1 -1
- package/dist/mcpFacet.d.mts +2 -2
- package/dist/mcpWidget.cjs +0 -0
- package/dist/mcpWidget.d.mts +2 -0
- package/dist/mcpWidget.mjs +1 -0
- package/dist/src/schemas/mcpBundle.cjs +56 -31
- package/dist/src/schemas/mcpBundle.cjs.map +1 -1
- package/dist/src/schemas/mcpBundle.d.mts +12 -0
- package/dist/src/schemas/mcpBundle.d.mts.map +1 -1
- package/dist/src/schemas/mcpBundle.mjs +56 -31
- package/dist/src/schemas/mcpBundle.mjs.map +1 -1
- package/dist/src/server/mcpBundleLoader.cjs +60 -1
- package/dist/src/server/mcpBundleLoader.cjs.map +1 -1
- package/dist/src/server/mcpBundleLoader.mjs +60 -1
- package/dist/src/server/mcpBundleLoader.mjs.map +1 -1
- package/dist/src/server/sessionMcpHandler.cjs +18 -4
- package/dist/src/server/sessionMcpHandler.cjs.map +1 -1
- package/dist/src/server/sessionMcpHandler.d.mts.map +1 -1
- package/dist/src/server/sessionMcpHandler.mjs +18 -4
- package/dist/src/server/sessionMcpHandler.mjs.map +1 -1
- package/dist/src/services/openSessionRegistry/index.cjs +14 -3
- package/dist/src/services/openSessionRegistry/index.cjs.map +1 -1
- package/dist/src/services/openSessionRegistry/index.d.mts +4 -0
- package/dist/src/services/openSessionRegistry/index.d.mts.map +1 -1
- package/dist/src/services/openSessionRegistry/index.mjs +14 -3
- package/dist/src/services/openSessionRegistry/index.mjs.map +1 -1
- package/dist/src/services/piExtensionHost/index.cjs +1 -1
- package/dist/src/services/piExtensionHost/index.mjs +1 -1
- package/dist/src/systems/main/adapters/headlessSessionHost.cjs +2 -2
- package/dist/src/systems/main/adapters/headlessSessionHost.cjs.map +1 -1
- package/dist/src/systems/main/adapters/headlessSessionHost.mjs +2 -2
- package/dist/src/systems/main/adapters/headlessSessionHost.mjs.map +1 -1
- package/dist/src/web/services/mcpWidget.cjs +9 -0
- package/dist/src/web/services/mcpWidget.cjs.map +1 -0
- package/dist/src/web/services/mcpWidget.d.mts +6 -0
- package/dist/src/web/services/mcpWidget.d.mts.map +1 -0
- package/dist/src/web/services/mcpWidget.mjs +9 -0
- package/dist/src/web/services/mcpWidget.mjs.map +1 -0
- package/dist/src/web/types/mcpWidget.d.mts +14 -0
- package/dist/src/web/types/mcpWidget.d.mts.map +1 -0
- package/dist/syncLocation.cjs +37 -4
- package/dist/syncLocation.cjs.map +1 -1
- package/dist/syncLocation.d.mts +4 -0
- package/dist/syncLocation.d.mts.map +1 -1
- package/dist/syncLocation.mjs +36 -5
- package/dist/syncLocation.mjs.map +1 -1
- package/dist/syncRegistration.cjs +4 -3
- package/dist/syncRegistration.cjs.map +1 -1
- package/dist/syncRegistration.d.mts +1 -0
- package/dist/syncRegistration.d.mts.map +1 -1
- package/dist/syncRegistration.mjs +4 -4
- package/dist/syncRegistration.mjs.map +1 -1
- package/dist/terminalPiChildSessionService.cjs +1 -1
- package/dist/terminalPiChildSessionService.mjs +1 -1
- package/dist/web.cjs +2 -0
- package/dist/web.d.mts +3 -1
- package/dist/web.mjs +2 -1
- package/dist/webCompositions.d.mts +1 -1
- package/package.json +10 -5
|
@@ -40,6 +40,8 @@ export interface HeadlessSessionHostOptions {
|
|
|
40
40
|
* a no-op when the worktree has no valid sync registration.
|
|
41
41
|
*/
|
|
42
42
|
piExtensions?: boolean;
|
|
43
|
+
/** Exact admitted bootstrap paths, used when a child inherits another worktree's generation. */
|
|
44
|
+
piExtensionPaths?: readonly string[];
|
|
43
45
|
}
|
|
44
46
|
export interface HeadlessSessionHost {
|
|
45
47
|
readonly runtime: DirectHarnessRuntime;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headlessSessionHost.d.mts","names":[],"sources":["../src/systems/main/types/headlessSessionHost.ts"],"mappings":";;;;;;;;;;;;;;;;iBAaiB;EACf;EACA;EACA;EACA;EACA,iBAAiB;EACjB;EACA;EACA;EACA;EACA,aAAa,SAAS;EACtB,WAAW;EACX;EACA,2BAA2B,QAAQ,yBAAyB,QAAQ,QAAQ;EAC5E,qBAAqB;;EAErB,sBAAsB;EACtB,mBAAmB;EACnB,0BACE,YAAY,gBAAgB,mCAC5B,WAAW;EAEb,iBAAiB,SAAS,SAAS,WAAW,0BAA0B;EACxE,YAAY;;;;;EAKZ;;iBAGe;WACN,SAAS;WACT,MAAM;WACN,aAAa;WACb,YAAY;WACZ,gBAAgB,MAAM;WACtB,iBAAiB,WAAW,0BAA0B;WACtD;EACT,oBAAoB,WAAW,OAAO;EACtC,qBAAqB,OAAO;EAC5B,WAAW"}
|
|
1
|
+
{"version":3,"file":"headlessSessionHost.d.mts","names":[],"sources":["../src/systems/main/types/headlessSessionHost.ts"],"mappings":";;;;;;;;;;;;;;;;iBAaiB;EACf;EACA;EACA;EACA;EACA,iBAAiB;EACjB;EACA;EACA;EACA;EACA,aAAa,SAAS;EACtB,WAAW;EACX;EACA,2BAA2B,QAAQ,yBAAyB,QAAQ,QAAQ;EAC5E,qBAAqB;;EAErB,sBAAsB;EACtB,mBAAmB;EACnB,0BACE,YAAY,gBAAgB,mCAC5B,WAAW;EAEb,iBAAiB,SAAS,SAAS,WAAW,0BAA0B;EACxE,YAAY;;;;;EAKZ;;EAEA;;iBAGe;WACN,SAAS;WACT,MAAM;WACN,aAAa;WACb,YAAY;WACZ,gBAAgB,MAAM;WACtB,iBAAiB,WAAW,0BAA0B;WACtD;EACT,oBAAoB,WAAW,OAAO;EACtC,qBAAqB,OAAO;EAC5B,WAAW"}
|
package/dist/mcpFacet.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { DoomMcpContextSnapshot, DoomMcpPluginContext, DoomMcpPluginDefinition, DoomMcpServiceScope, DoomMcpSessionPlugin, DoomMcpSkill, DoomMcpUiResource, defineMcpPlugin, defineMcpSkill, defineMcpTool, defineMcpUiResource } from "./src/schemas/mcpFacet.mjs";
|
|
2
|
-
import { DOOM_MCP_BUNDLE_FILE, DOOM_MCP_BUNDLE_VERSION, DoomMcpBundle, DoomMcpBundleEntry, parseDoomMcpBundle } from "./src/schemas/mcpBundle.mjs";
|
|
2
|
+
import { DOOM_MCP_BUNDLE_FILE, DOOM_MCP_BUNDLE_VERSION, DoomMcpBundle, DoomMcpBundleEntry, DoomMcpUiBundle, parseDoomMcpBundle } from "./src/schemas/mcpBundle.mjs";
|
|
3
3
|
import { LoadMcpBundleOptions, LoadedMcpBundle, LoadedMcpPlugin, loadMcpBundle } from "./src/server/mcpBundleLoader.mjs";
|
|
4
|
-
export { DOOM_MCP_BUNDLE_FILE, DOOM_MCP_BUNDLE_VERSION, type DoomMcpBundle, type DoomMcpBundleEntry, type DoomMcpContextSnapshot, type DoomMcpPluginContext, type DoomMcpPluginDefinition, type DoomMcpServiceScope, type DoomMcpSessionPlugin, type DoomMcpSkill, type DoomMcpUiResource, type LoadMcpBundleOptions, type LoadedMcpBundle, type LoadedMcpPlugin, defineMcpPlugin, defineMcpSkill, defineMcpTool, defineMcpUiResource, loadMcpBundle, parseDoomMcpBundle };
|
|
4
|
+
export { DOOM_MCP_BUNDLE_FILE, DOOM_MCP_BUNDLE_VERSION, type DoomMcpBundle, type DoomMcpBundleEntry, type DoomMcpContextSnapshot, type DoomMcpPluginContext, type DoomMcpPluginDefinition, type DoomMcpServiceScope, type DoomMcpSessionPlugin, type DoomMcpSkill, type DoomMcpUiBundle, type DoomMcpUiResource, type LoadMcpBundleOptions, type LoadedMcpBundle, type LoadedMcpPlugin, defineMcpPlugin, defineMcpSkill, defineMcpTool, defineMcpUiResource, loadMcpBundle, parseDoomMcpBundle };
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -22,41 +22,66 @@ function parseDoomMcpBundle(value) {
|
|
|
22
22
|
if (!/^[a-f0-9]{64}$/u.test(fingerprint)) throw new Error("Invalid MCP bundle fingerprint");
|
|
23
23
|
if (!Array.isArray(value.entries)) throw new Error("Invalid MCP bundle entries");
|
|
24
24
|
const names = /* @__PURE__ */ new Set();
|
|
25
|
+
const entries = value.entries.map((candidate) => {
|
|
26
|
+
if (!record(candidate)) throw new Error("Invalid MCP bundle entry");
|
|
27
|
+
const packageName = text(candidate.packageName, "packageName");
|
|
28
|
+
if (names.has(packageName)) throw new Error(`Duplicate MCP bundle package: ${packageName}`);
|
|
29
|
+
names.add(packageName);
|
|
30
|
+
const entry = relativeFile(candidate.entry, `${packageName} entry`);
|
|
31
|
+
const module = relativeFile(candidate.module, `${packageName} module`);
|
|
32
|
+
if (!module.endsWith(".mjs")) throw new Error(`Invalid MCP bundle ${packageName} module: expected .mjs`);
|
|
33
|
+
const sha256 = text(candidate.sha256, `${packageName} sha256`);
|
|
34
|
+
if (!/^[a-f0-9]{64}$/u.test(sha256)) throw new Error(`Invalid MCP bundle ${packageName} sha256`);
|
|
35
|
+
if (!Array.isArray(candidate.owners) || candidate.owners.length === 0) throw new Error(`Invalid MCP bundle ${packageName} owners`);
|
|
36
|
+
const ownership = /* @__PURE__ */ new Set();
|
|
37
|
+
const owners = candidate.owners.map((owner) => {
|
|
38
|
+
if (!record(owner)) throw new Error(`Invalid MCP bundle ${packageName} owner`);
|
|
39
|
+
const majorMode = text(owner.majorMode, `${packageName} majorMode`);
|
|
40
|
+
const layer = text(owner.layer, `${packageName} layer`);
|
|
41
|
+
const key = JSON.stringify([majorMode, layer]);
|
|
42
|
+
if (ownership.has(key)) throw new Error(`Duplicate MCP bundle ${packageName} owner`);
|
|
43
|
+
ownership.add(key);
|
|
44
|
+
return {
|
|
45
|
+
majorMode,
|
|
46
|
+
layer
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
const widgets = candidate.widgets;
|
|
50
|
+
if (widgets !== void 0 && (!Array.isArray(widgets) || widgets.some((key) => typeof key !== "string" || !key.startsWith(`${packageName}/`) || key.length <= packageName.length + 1 || key.includes("\0")) || new Set(widgets).size !== widgets.length)) throw new Error(`Invalid MCP bundle ${packageName} widgets`);
|
|
51
|
+
return {
|
|
52
|
+
packageName,
|
|
53
|
+
entry,
|
|
54
|
+
module,
|
|
55
|
+
sha256,
|
|
56
|
+
owners,
|
|
57
|
+
...widgets === void 0 ? {} : { widgets }
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
let ui;
|
|
61
|
+
if (value.ui !== void 0) {
|
|
62
|
+
if (!record(value.ui) || typeof value.ui.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(value.ui.sha256) || !Array.isArray(value.ui.inputs)) throw new Error("Invalid MCP UI bundle");
|
|
63
|
+
const file = relativeFile(value.ui.file, "UI file");
|
|
64
|
+
if (!file.endsWith(".html")) throw new Error("Invalid MCP UI bundle file");
|
|
65
|
+
const inputs = value.ui.inputs.map((input) => {
|
|
66
|
+
if (!record(input) || typeof input.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(input.sha256)) throw new Error("Invalid MCP UI input");
|
|
67
|
+
return {
|
|
68
|
+
path: text(input.path, "UI input path"),
|
|
69
|
+
sha256: input.sha256
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
ui = {
|
|
73
|
+
file,
|
|
74
|
+
sha256: value.ui.sha256,
|
|
75
|
+
inputs
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (entries.some((entry) => (entry.widgets?.length ?? 0) > 0) && ui === void 0) throw new Error("Missing composed MCP UI resource");
|
|
25
79
|
return {
|
|
26
80
|
version: 1,
|
|
27
81
|
generation,
|
|
28
82
|
fingerprint,
|
|
29
|
-
entries
|
|
30
|
-
|
|
31
|
-
const packageName = text(candidate.packageName, "packageName");
|
|
32
|
-
if (names.has(packageName)) throw new Error(`Duplicate MCP bundle package: ${packageName}`);
|
|
33
|
-
names.add(packageName);
|
|
34
|
-
const entry = relativeFile(candidate.entry, `${packageName} entry`);
|
|
35
|
-
const module = relativeFile(candidate.module, `${packageName} module`);
|
|
36
|
-
if (!module.endsWith(".mjs")) throw new Error(`Invalid MCP bundle ${packageName} module: expected .mjs`);
|
|
37
|
-
const sha256 = text(candidate.sha256, `${packageName} sha256`);
|
|
38
|
-
if (!/^[a-f0-9]{64}$/u.test(sha256)) throw new Error(`Invalid MCP bundle ${packageName} sha256`);
|
|
39
|
-
if (!Array.isArray(candidate.owners) || candidate.owners.length === 0) throw new Error(`Invalid MCP bundle ${packageName} owners`);
|
|
40
|
-
const ownership = /* @__PURE__ */ new Set();
|
|
41
|
-
return {
|
|
42
|
-
packageName,
|
|
43
|
-
entry,
|
|
44
|
-
module,
|
|
45
|
-
sha256,
|
|
46
|
-
owners: candidate.owners.map((owner) => {
|
|
47
|
-
if (!record(owner)) throw new Error(`Invalid MCP bundle ${packageName} owner`);
|
|
48
|
-
const majorMode = text(owner.majorMode, `${packageName} majorMode`);
|
|
49
|
-
const layer = text(owner.layer, `${packageName} layer`);
|
|
50
|
-
const key = JSON.stringify([majorMode, layer]);
|
|
51
|
-
if (ownership.has(key)) throw new Error(`Duplicate MCP bundle ${packageName} owner`);
|
|
52
|
-
ownership.add(key);
|
|
53
|
-
return {
|
|
54
|
-
majorMode,
|
|
55
|
-
layer
|
|
56
|
-
};
|
|
57
|
-
})
|
|
58
|
-
};
|
|
59
|
-
})
|
|
83
|
+
entries,
|
|
84
|
+
...ui === void 0 ? {} : { ui }
|
|
60
85
|
};
|
|
61
86
|
}
|
|
62
87
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcpBundle.cjs","names":[],"sources":["../../../src/schemas/mcpBundle.ts"],"sourcesContent":["import type { DoomServerBundleOwner } from './serverBundle';\n\nexport const DOOM_MCP_BUNDLE_VERSION = 1;\nexport const DOOM_MCP_BUNDLE_FILE = 'mcp.bundle.json';\n\nexport interface DoomMcpBundleEntry {\n readonly packageName: string;\n readonly entry: string;\n readonly module: string;\n readonly sha256: string;\n readonly owners: readonly DoomServerBundleOwner[];\n}\n\nexport interface DoomMcpBundle {\n readonly version: typeof DOOM_MCP_BUNDLE_VERSION;\n readonly generation: string;\n readonly fingerprint: string;\n readonly entries: readonly DoomMcpBundleEntry[];\n}\n\nfunction record(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction text(value: unknown, field: string): string {\n if (typeof value !== 'string' || value.trim() === '' || value.includes('\\0'))\n throw new Error(`Invalid MCP bundle ${field}`);\n return value;\n}\n\nfunction relativeFile(value: unknown, field: string): string {\n const file = text(value, field);\n const segments = file.slice(2).split('/');\n if (\n !file.startsWith('./') ||\n /[\\\\%?#]/u.test(file) ||\n segments.some((segment) => segment === '' || segment === '.' || segment === '..')\n )\n throw new Error(`Invalid MCP bundle ${field}: expected a contained ./file path`);\n return file;\n}\n\n/** Validates the admitted descriptor before importing any package code. */\nexport function parseDoomMcpBundle(value: unknown): DoomMcpBundle {\n if (!record(value) || value.version !== DOOM_MCP_BUNDLE_VERSION) throw new Error('Unsupported MCP bundle version');\n const generation = text(value.generation, 'generation');\n const fingerprint = text(value.fingerprint, 'fingerprint');\n if (!/^[a-f0-9]{64}$/u.test(fingerprint)) throw new Error('Invalid MCP bundle fingerprint');\n if (!Array.isArray(value.entries)) throw new Error('Invalid MCP bundle entries');\n const names = new Set<string>();\n const entries = value.entries.map((candidate: unknown): DoomMcpBundleEntry => {\n if (!record(candidate)) throw new Error('Invalid MCP bundle entry');\n const packageName = text(candidate.packageName, 'packageName');\n if (names.has(packageName)) throw new Error(`Duplicate MCP bundle package: ${packageName}`);\n names.add(packageName);\n const entry = relativeFile(candidate.entry, `${packageName} entry`);\n const module = relativeFile(candidate.module, `${packageName} module`);\n if (!module.endsWith('.mjs')) throw new Error(`Invalid MCP bundle ${packageName} module: expected .mjs`);\n const sha256 = text(candidate.sha256, `${packageName} sha256`);\n if (!/^[a-f0-9]{64}$/u.test(sha256)) throw new Error(`Invalid MCP bundle ${packageName} sha256`);\n if (!Array.isArray(candidate.owners) || candidate.owners.length === 0)\n throw new Error(`Invalid MCP bundle ${packageName} owners`);\n const ownership = new Set<string>();\n const owners = candidate.owners.map((owner: unknown): DoomServerBundleOwner => {\n if (!record(owner)) throw new Error(`Invalid MCP bundle ${packageName} owner`);\n const majorMode = text(owner.majorMode, `${packageName} majorMode`);\n const layer = text(owner.layer, `${packageName} layer`);\n const key = JSON.stringify([majorMode, layer]);\n if (ownership.has(key)) throw new Error(`Duplicate MCP bundle ${packageName} owner`);\n ownership.add(key);\n return { majorMode, layer };\n });\n
|
|
1
|
+
{"version":3,"file":"mcpBundle.cjs","names":[],"sources":["../../../src/schemas/mcpBundle.ts"],"sourcesContent":["import type { DoomServerBundleOwner } from './serverBundle';\n\nexport const DOOM_MCP_BUNDLE_VERSION = 1;\nexport const DOOM_MCP_BUNDLE_FILE = 'mcp.bundle.json';\n\nexport interface DoomMcpBundleEntry {\n readonly packageName: string;\n readonly entry: string;\n readonly module: string;\n readonly sha256: string;\n readonly owners: readonly DoomServerBundleOwner[];\n /** Package-owned component keys generated from frontend tool declarations. */\n readonly widgets?: readonly string[];\n}\n\nexport interface DoomMcpUiBundle {\n readonly file: string;\n readonly sha256: string;\n /** Build inputs are checked for sync freshness, never exposed in the UI resource. */\n readonly inputs: readonly { readonly path: string; readonly sha256: string }[];\n}\n\nexport interface DoomMcpBundle {\n readonly version: typeof DOOM_MCP_BUNDLE_VERSION;\n readonly generation: string;\n readonly fingerprint: string;\n readonly entries: readonly DoomMcpBundleEntry[];\n readonly ui?: DoomMcpUiBundle;\n}\n\nfunction record(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction text(value: unknown, field: string): string {\n if (typeof value !== 'string' || value.trim() === '' || value.includes('\\0'))\n throw new Error(`Invalid MCP bundle ${field}`);\n return value;\n}\n\nfunction relativeFile(value: unknown, field: string): string {\n const file = text(value, field);\n const segments = file.slice(2).split('/');\n if (\n !file.startsWith('./') ||\n /[\\\\%?#]/u.test(file) ||\n segments.some((segment) => segment === '' || segment === '.' || segment === '..')\n )\n throw new Error(`Invalid MCP bundle ${field}: expected a contained ./file path`);\n return file;\n}\n\n/** Validates the admitted descriptor before importing any package code. */\nexport function parseDoomMcpBundle(value: unknown): DoomMcpBundle {\n if (!record(value) || value.version !== DOOM_MCP_BUNDLE_VERSION) throw new Error('Unsupported MCP bundle version');\n const generation = text(value.generation, 'generation');\n const fingerprint = text(value.fingerprint, 'fingerprint');\n if (!/^[a-f0-9]{64}$/u.test(fingerprint)) throw new Error('Invalid MCP bundle fingerprint');\n if (!Array.isArray(value.entries)) throw new Error('Invalid MCP bundle entries');\n const names = new Set<string>();\n const entries = value.entries.map((candidate: unknown): DoomMcpBundleEntry => {\n if (!record(candidate)) throw new Error('Invalid MCP bundle entry');\n const packageName = text(candidate.packageName, 'packageName');\n if (names.has(packageName)) throw new Error(`Duplicate MCP bundle package: ${packageName}`);\n names.add(packageName);\n const entry = relativeFile(candidate.entry, `${packageName} entry`);\n const module = relativeFile(candidate.module, `${packageName} module`);\n if (!module.endsWith('.mjs')) throw new Error(`Invalid MCP bundle ${packageName} module: expected .mjs`);\n const sha256 = text(candidate.sha256, `${packageName} sha256`);\n if (!/^[a-f0-9]{64}$/u.test(sha256)) throw new Error(`Invalid MCP bundle ${packageName} sha256`);\n if (!Array.isArray(candidate.owners) || candidate.owners.length === 0)\n throw new Error(`Invalid MCP bundle ${packageName} owners`);\n const ownership = new Set<string>();\n const owners = candidate.owners.map((owner: unknown): DoomServerBundleOwner => {\n if (!record(owner)) throw new Error(`Invalid MCP bundle ${packageName} owner`);\n const majorMode = text(owner.majorMode, `${packageName} majorMode`);\n const layer = text(owner.layer, `${packageName} layer`);\n const key = JSON.stringify([majorMode, layer]);\n if (ownership.has(key)) throw new Error(`Duplicate MCP bundle ${packageName} owner`);\n ownership.add(key);\n return { majorMode, layer };\n });\n const widgets = candidate.widgets;\n if (\n widgets !== undefined &&\n (!Array.isArray(widgets) ||\n widgets.some(\n (key: unknown) =>\n typeof key !== 'string' ||\n !key.startsWith(`${packageName}/`) ||\n key.length <= packageName.length + 1 ||\n key.includes('\\0'),\n ) ||\n new Set(widgets).size !== widgets.length)\n )\n throw new Error(`Invalid MCP bundle ${packageName} widgets`);\n return {\n packageName,\n entry,\n module,\n sha256,\n owners,\n ...(widgets === undefined ? {} : { widgets: widgets as string[] }),\n };\n });\n let ui: DoomMcpUiBundle | undefined;\n if (value.ui !== undefined) {\n if (\n !record(value.ui) ||\n typeof value.ui.sha256 !== 'string' ||\n !/^[a-f0-9]{64}$/u.test(value.ui.sha256) ||\n !Array.isArray(value.ui.inputs)\n )\n throw new Error('Invalid MCP UI bundle');\n const file = relativeFile(value.ui.file, 'UI file');\n if (!file.endsWith('.html')) throw new Error('Invalid MCP UI bundle file');\n const inputs = value.ui.inputs.map((input: unknown) => {\n if (!record(input) || typeof input.sha256 !== 'string' || !/^[a-f0-9]{64}$/u.test(input.sha256))\n throw new Error('Invalid MCP UI input');\n return { path: text(input.path, 'UI input path'), sha256: input.sha256 };\n });\n ui = { file, sha256: value.ui.sha256, inputs };\n }\n if (entries.some((entry) => (entry.widgets?.length ?? 0) > 0) && ui === undefined)\n throw new Error('Missing composed MCP UI resource');\n return { version: DOOM_MCP_BUNDLE_VERSION, generation, fingerprint, entries, ...(ui === undefined ? {} : { ui }) };\n}\n"],"mappings":"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"}
|
|
@@ -8,12 +8,24 @@ export interface DoomMcpBundleEntry {
|
|
|
8
8
|
readonly module: string;
|
|
9
9
|
readonly sha256: string;
|
|
10
10
|
readonly owners: readonly DoomServerBundleOwner[];
|
|
11
|
+
/** Package-owned component keys generated from frontend tool declarations. */
|
|
12
|
+
readonly widgets?: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
export interface DoomMcpUiBundle {
|
|
15
|
+
readonly file: string;
|
|
16
|
+
readonly sha256: string;
|
|
17
|
+
/** Build inputs are checked for sync freshness, never exposed in the UI resource. */
|
|
18
|
+
readonly inputs: readonly {
|
|
19
|
+
readonly path: string;
|
|
20
|
+
readonly sha256: string;
|
|
21
|
+
}[];
|
|
11
22
|
}
|
|
12
23
|
export interface DoomMcpBundle {
|
|
13
24
|
readonly version: typeof DOOM_MCP_BUNDLE_VERSION;
|
|
14
25
|
readonly generation: string;
|
|
15
26
|
readonly fingerprint: string;
|
|
16
27
|
readonly entries: readonly DoomMcpBundleEntry[];
|
|
28
|
+
readonly ui?: DoomMcpUiBundle;
|
|
17
29
|
}
|
|
18
30
|
/** Validates the admitted descriptor before importing any package code. */
|
|
19
31
|
export declare function parseDoomMcpBundle(value: unknown): DoomMcpBundle;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcpBundle.d.mts","names":[],"sources":["../../../src/schemas/mcpBundle.ts"],"mappings":";;qBAEa;qBACA;iBAEI;WACN;WACA;WACA;WACA;WACA,iBAAiB;;
|
|
1
|
+
{"version":3,"file":"mcpBundle.d.mts","names":[],"sources":["../../../src/schemas/mcpBundle.ts"],"mappings":";;qBAEa;qBACA;iBAEI;WACN;WACA;WACA;WACA;WACA,iBAAiB;;WAEjB;;iBAGM;WACN;WACA;;WAEA;aAA4B;aAAuB;;;iBAG7C;WACN,gBAAgB;WAChB;WACA;WACA,kBAAkB;WAClB,KAAK;;;wBA0BA,mBAAmB,iBAAiB"}
|
|
@@ -22,41 +22,66 @@ function parseDoomMcpBundle(value) {
|
|
|
22
22
|
if (!/^[a-f0-9]{64}$/u.test(fingerprint)) throw new Error("Invalid MCP bundle fingerprint");
|
|
23
23
|
if (!Array.isArray(value.entries)) throw new Error("Invalid MCP bundle entries");
|
|
24
24
|
const names = /* @__PURE__ */ new Set();
|
|
25
|
+
const entries = value.entries.map((candidate) => {
|
|
26
|
+
if (!record(candidate)) throw new Error("Invalid MCP bundle entry");
|
|
27
|
+
const packageName = text(candidate.packageName, "packageName");
|
|
28
|
+
if (names.has(packageName)) throw new Error(`Duplicate MCP bundle package: ${packageName}`);
|
|
29
|
+
names.add(packageName);
|
|
30
|
+
const entry = relativeFile(candidate.entry, `${packageName} entry`);
|
|
31
|
+
const module = relativeFile(candidate.module, `${packageName} module`);
|
|
32
|
+
if (!module.endsWith(".mjs")) throw new Error(`Invalid MCP bundle ${packageName} module: expected .mjs`);
|
|
33
|
+
const sha256 = text(candidate.sha256, `${packageName} sha256`);
|
|
34
|
+
if (!/^[a-f0-9]{64}$/u.test(sha256)) throw new Error(`Invalid MCP bundle ${packageName} sha256`);
|
|
35
|
+
if (!Array.isArray(candidate.owners) || candidate.owners.length === 0) throw new Error(`Invalid MCP bundle ${packageName} owners`);
|
|
36
|
+
const ownership = /* @__PURE__ */ new Set();
|
|
37
|
+
const owners = candidate.owners.map((owner) => {
|
|
38
|
+
if (!record(owner)) throw new Error(`Invalid MCP bundle ${packageName} owner`);
|
|
39
|
+
const majorMode = text(owner.majorMode, `${packageName} majorMode`);
|
|
40
|
+
const layer = text(owner.layer, `${packageName} layer`);
|
|
41
|
+
const key = JSON.stringify([majorMode, layer]);
|
|
42
|
+
if (ownership.has(key)) throw new Error(`Duplicate MCP bundle ${packageName} owner`);
|
|
43
|
+
ownership.add(key);
|
|
44
|
+
return {
|
|
45
|
+
majorMode,
|
|
46
|
+
layer
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
const widgets = candidate.widgets;
|
|
50
|
+
if (widgets !== void 0 && (!Array.isArray(widgets) || widgets.some((key) => typeof key !== "string" || !key.startsWith(`${packageName}/`) || key.length <= packageName.length + 1 || key.includes("\0")) || new Set(widgets).size !== widgets.length)) throw new Error(`Invalid MCP bundle ${packageName} widgets`);
|
|
51
|
+
return {
|
|
52
|
+
packageName,
|
|
53
|
+
entry,
|
|
54
|
+
module,
|
|
55
|
+
sha256,
|
|
56
|
+
owners,
|
|
57
|
+
...widgets === void 0 ? {} : { widgets }
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
let ui;
|
|
61
|
+
if (value.ui !== void 0) {
|
|
62
|
+
if (!record(value.ui) || typeof value.ui.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(value.ui.sha256) || !Array.isArray(value.ui.inputs)) throw new Error("Invalid MCP UI bundle");
|
|
63
|
+
const file = relativeFile(value.ui.file, "UI file");
|
|
64
|
+
if (!file.endsWith(".html")) throw new Error("Invalid MCP UI bundle file");
|
|
65
|
+
const inputs = value.ui.inputs.map((input) => {
|
|
66
|
+
if (!record(input) || typeof input.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(input.sha256)) throw new Error("Invalid MCP UI input");
|
|
67
|
+
return {
|
|
68
|
+
path: text(input.path, "UI input path"),
|
|
69
|
+
sha256: input.sha256
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
ui = {
|
|
73
|
+
file,
|
|
74
|
+
sha256: value.ui.sha256,
|
|
75
|
+
inputs
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (entries.some((entry) => (entry.widgets?.length ?? 0) > 0) && ui === void 0) throw new Error("Missing composed MCP UI resource");
|
|
25
79
|
return {
|
|
26
80
|
version: 1,
|
|
27
81
|
generation,
|
|
28
82
|
fingerprint,
|
|
29
|
-
entries
|
|
30
|
-
|
|
31
|
-
const packageName = text(candidate.packageName, "packageName");
|
|
32
|
-
if (names.has(packageName)) throw new Error(`Duplicate MCP bundle package: ${packageName}`);
|
|
33
|
-
names.add(packageName);
|
|
34
|
-
const entry = relativeFile(candidate.entry, `${packageName} entry`);
|
|
35
|
-
const module = relativeFile(candidate.module, `${packageName} module`);
|
|
36
|
-
if (!module.endsWith(".mjs")) throw new Error(`Invalid MCP bundle ${packageName} module: expected .mjs`);
|
|
37
|
-
const sha256 = text(candidate.sha256, `${packageName} sha256`);
|
|
38
|
-
if (!/^[a-f0-9]{64}$/u.test(sha256)) throw new Error(`Invalid MCP bundle ${packageName} sha256`);
|
|
39
|
-
if (!Array.isArray(candidate.owners) || candidate.owners.length === 0) throw new Error(`Invalid MCP bundle ${packageName} owners`);
|
|
40
|
-
const ownership = /* @__PURE__ */ new Set();
|
|
41
|
-
return {
|
|
42
|
-
packageName,
|
|
43
|
-
entry,
|
|
44
|
-
module,
|
|
45
|
-
sha256,
|
|
46
|
-
owners: candidate.owners.map((owner) => {
|
|
47
|
-
if (!record(owner)) throw new Error(`Invalid MCP bundle ${packageName} owner`);
|
|
48
|
-
const majorMode = text(owner.majorMode, `${packageName} majorMode`);
|
|
49
|
-
const layer = text(owner.layer, `${packageName} layer`);
|
|
50
|
-
const key = JSON.stringify([majorMode, layer]);
|
|
51
|
-
if (ownership.has(key)) throw new Error(`Duplicate MCP bundle ${packageName} owner`);
|
|
52
|
-
ownership.add(key);
|
|
53
|
-
return {
|
|
54
|
-
majorMode,
|
|
55
|
-
layer
|
|
56
|
-
};
|
|
57
|
-
})
|
|
58
|
-
};
|
|
59
|
-
})
|
|
83
|
+
entries,
|
|
84
|
+
...ui === void 0 ? {} : { ui }
|
|
60
85
|
};
|
|
61
86
|
}
|
|
62
87
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcpBundle.mjs","names":[],"sources":["../../../src/schemas/mcpBundle.ts"],"sourcesContent":["import type { DoomServerBundleOwner } from './serverBundle';\n\nexport const DOOM_MCP_BUNDLE_VERSION = 1;\nexport const DOOM_MCP_BUNDLE_FILE = 'mcp.bundle.json';\n\nexport interface DoomMcpBundleEntry {\n readonly packageName: string;\n readonly entry: string;\n readonly module: string;\n readonly sha256: string;\n readonly owners: readonly DoomServerBundleOwner[];\n}\n\nexport interface DoomMcpBundle {\n readonly version: typeof DOOM_MCP_BUNDLE_VERSION;\n readonly generation: string;\n readonly fingerprint: string;\n readonly entries: readonly DoomMcpBundleEntry[];\n}\n\nfunction record(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction text(value: unknown, field: string): string {\n if (typeof value !== 'string' || value.trim() === '' || value.includes('\\0'))\n throw new Error(`Invalid MCP bundle ${field}`);\n return value;\n}\n\nfunction relativeFile(value: unknown, field: string): string {\n const file = text(value, field);\n const segments = file.slice(2).split('/');\n if (\n !file.startsWith('./') ||\n /[\\\\%?#]/u.test(file) ||\n segments.some((segment) => segment === '' || segment === '.' || segment === '..')\n )\n throw new Error(`Invalid MCP bundle ${field}: expected a contained ./file path`);\n return file;\n}\n\n/** Validates the admitted descriptor before importing any package code. */\nexport function parseDoomMcpBundle(value: unknown): DoomMcpBundle {\n if (!record(value) || value.version !== DOOM_MCP_BUNDLE_VERSION) throw new Error('Unsupported MCP bundle version');\n const generation = text(value.generation, 'generation');\n const fingerprint = text(value.fingerprint, 'fingerprint');\n if (!/^[a-f0-9]{64}$/u.test(fingerprint)) throw new Error('Invalid MCP bundle fingerprint');\n if (!Array.isArray(value.entries)) throw new Error('Invalid MCP bundle entries');\n const names = new Set<string>();\n const entries = value.entries.map((candidate: unknown): DoomMcpBundleEntry => {\n if (!record(candidate)) throw new Error('Invalid MCP bundle entry');\n const packageName = text(candidate.packageName, 'packageName');\n if (names.has(packageName)) throw new Error(`Duplicate MCP bundle package: ${packageName}`);\n names.add(packageName);\n const entry = relativeFile(candidate.entry, `${packageName} entry`);\n const module = relativeFile(candidate.module, `${packageName} module`);\n if (!module.endsWith('.mjs')) throw new Error(`Invalid MCP bundle ${packageName} module: expected .mjs`);\n const sha256 = text(candidate.sha256, `${packageName} sha256`);\n if (!/^[a-f0-9]{64}$/u.test(sha256)) throw new Error(`Invalid MCP bundle ${packageName} sha256`);\n if (!Array.isArray(candidate.owners) || candidate.owners.length === 0)\n throw new Error(`Invalid MCP bundle ${packageName} owners`);\n const ownership = new Set<string>();\n const owners = candidate.owners.map((owner: unknown): DoomServerBundleOwner => {\n if (!record(owner)) throw new Error(`Invalid MCP bundle ${packageName} owner`);\n const majorMode = text(owner.majorMode, `${packageName} majorMode`);\n const layer = text(owner.layer, `${packageName} layer`);\n const key = JSON.stringify([majorMode, layer]);\n if (ownership.has(key)) throw new Error(`Duplicate MCP bundle ${packageName} owner`);\n ownership.add(key);\n return { majorMode, layer };\n });\n
|
|
1
|
+
{"version":3,"file":"mcpBundle.mjs","names":[],"sources":["../../../src/schemas/mcpBundle.ts"],"sourcesContent":["import type { DoomServerBundleOwner } from './serverBundle';\n\nexport const DOOM_MCP_BUNDLE_VERSION = 1;\nexport const DOOM_MCP_BUNDLE_FILE = 'mcp.bundle.json';\n\nexport interface DoomMcpBundleEntry {\n readonly packageName: string;\n readonly entry: string;\n readonly module: string;\n readonly sha256: string;\n readonly owners: readonly DoomServerBundleOwner[];\n /** Package-owned component keys generated from frontend tool declarations. */\n readonly widgets?: readonly string[];\n}\n\nexport interface DoomMcpUiBundle {\n readonly file: string;\n readonly sha256: string;\n /** Build inputs are checked for sync freshness, never exposed in the UI resource. */\n readonly inputs: readonly { readonly path: string; readonly sha256: string }[];\n}\n\nexport interface DoomMcpBundle {\n readonly version: typeof DOOM_MCP_BUNDLE_VERSION;\n readonly generation: string;\n readonly fingerprint: string;\n readonly entries: readonly DoomMcpBundleEntry[];\n readonly ui?: DoomMcpUiBundle;\n}\n\nfunction record(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction text(value: unknown, field: string): string {\n if (typeof value !== 'string' || value.trim() === '' || value.includes('\\0'))\n throw new Error(`Invalid MCP bundle ${field}`);\n return value;\n}\n\nfunction relativeFile(value: unknown, field: string): string {\n const file = text(value, field);\n const segments = file.slice(2).split('/');\n if (\n !file.startsWith('./') ||\n /[\\\\%?#]/u.test(file) ||\n segments.some((segment) => segment === '' || segment === '.' || segment === '..')\n )\n throw new Error(`Invalid MCP bundle ${field}: expected a contained ./file path`);\n return file;\n}\n\n/** Validates the admitted descriptor before importing any package code. */\nexport function parseDoomMcpBundle(value: unknown): DoomMcpBundle {\n if (!record(value) || value.version !== DOOM_MCP_BUNDLE_VERSION) throw new Error('Unsupported MCP bundle version');\n const generation = text(value.generation, 'generation');\n const fingerprint = text(value.fingerprint, 'fingerprint');\n if (!/^[a-f0-9]{64}$/u.test(fingerprint)) throw new Error('Invalid MCP bundle fingerprint');\n if (!Array.isArray(value.entries)) throw new Error('Invalid MCP bundle entries');\n const names = new Set<string>();\n const entries = value.entries.map((candidate: unknown): DoomMcpBundleEntry => {\n if (!record(candidate)) throw new Error('Invalid MCP bundle entry');\n const packageName = text(candidate.packageName, 'packageName');\n if (names.has(packageName)) throw new Error(`Duplicate MCP bundle package: ${packageName}`);\n names.add(packageName);\n const entry = relativeFile(candidate.entry, `${packageName} entry`);\n const module = relativeFile(candidate.module, `${packageName} module`);\n if (!module.endsWith('.mjs')) throw new Error(`Invalid MCP bundle ${packageName} module: expected .mjs`);\n const sha256 = text(candidate.sha256, `${packageName} sha256`);\n if (!/^[a-f0-9]{64}$/u.test(sha256)) throw new Error(`Invalid MCP bundle ${packageName} sha256`);\n if (!Array.isArray(candidate.owners) || candidate.owners.length === 0)\n throw new Error(`Invalid MCP bundle ${packageName} owners`);\n const ownership = new Set<string>();\n const owners = candidate.owners.map((owner: unknown): DoomServerBundleOwner => {\n if (!record(owner)) throw new Error(`Invalid MCP bundle ${packageName} owner`);\n const majorMode = text(owner.majorMode, `${packageName} majorMode`);\n const layer = text(owner.layer, `${packageName} layer`);\n const key = JSON.stringify([majorMode, layer]);\n if (ownership.has(key)) throw new Error(`Duplicate MCP bundle ${packageName} owner`);\n ownership.add(key);\n return { majorMode, layer };\n });\n const widgets = candidate.widgets;\n if (\n widgets !== undefined &&\n (!Array.isArray(widgets) ||\n widgets.some(\n (key: unknown) =>\n typeof key !== 'string' ||\n !key.startsWith(`${packageName}/`) ||\n key.length <= packageName.length + 1 ||\n key.includes('\\0'),\n ) ||\n new Set(widgets).size !== widgets.length)\n )\n throw new Error(`Invalid MCP bundle ${packageName} widgets`);\n return {\n packageName,\n entry,\n module,\n sha256,\n owners,\n ...(widgets === undefined ? {} : { widgets: widgets as string[] }),\n };\n });\n let ui: DoomMcpUiBundle | undefined;\n if (value.ui !== undefined) {\n if (\n !record(value.ui) ||\n typeof value.ui.sha256 !== 'string' ||\n !/^[a-f0-9]{64}$/u.test(value.ui.sha256) ||\n !Array.isArray(value.ui.inputs)\n )\n throw new Error('Invalid MCP UI bundle');\n const file = relativeFile(value.ui.file, 'UI file');\n if (!file.endsWith('.html')) throw new Error('Invalid MCP UI bundle file');\n const inputs = value.ui.inputs.map((input: unknown) => {\n if (!record(input) || typeof input.sha256 !== 'string' || !/^[a-f0-9]{64}$/u.test(input.sha256))\n throw new Error('Invalid MCP UI input');\n return { path: text(input.path, 'UI input path'), sha256: input.sha256 };\n });\n ui = { file, sha256: value.ui.sha256, inputs };\n }\n if (entries.some((entry) => (entry.widgets?.length ?? 0) > 0) && ui === undefined)\n throw new Error('Missing composed MCP UI resource');\n return { version: DOOM_MCP_BUNDLE_VERSION, generation, fingerprint, entries, ...(ui === undefined ? {} : { ui }) };\n}\n"],"mappings":"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"}
|
|
@@ -26,6 +26,32 @@ async function loadMcpBundle(options) {
|
|
|
26
26
|
if (node_crypto.default.createHash("sha256").update(descriptorBytes).digest("hex") !== options.descriptorSha256) throw new Error("MCP descriptor hash does not match the admitted descriptor");
|
|
27
27
|
const descriptor = require_mcpBundle.parseDoomMcpBundle(JSON.parse(descriptorBytes.toString("utf8")));
|
|
28
28
|
if (descriptor.generation !== options.generation || descriptor.fingerprint !== options.fingerprint) throw new Error("MCP bundle does not match the admitted generation");
|
|
29
|
+
let resource;
|
|
30
|
+
if (descriptor.ui) {
|
|
31
|
+
const html = node_fs.default.readFileSync(containedFile(directory, descriptor.ui.file), "utf8");
|
|
32
|
+
if (node_crypto.default.createHash("sha256").update(html).digest("hex") !== descriptor.ui.sha256) throw new Error("MCP UI hash does not match the admitted descriptor");
|
|
33
|
+
resource = {
|
|
34
|
+
uri: `ui://doompi/tools/${descriptor.ui.sha256}/index.html`,
|
|
35
|
+
name: "doompi-tool-widgets",
|
|
36
|
+
description: "Package-owned tool widgets composed by doompi sync.",
|
|
37
|
+
mimeType: "text/html;profile=mcp-app",
|
|
38
|
+
_meta: {
|
|
39
|
+
ui: {
|
|
40
|
+
csp: {
|
|
41
|
+
connectDomains: [],
|
|
42
|
+
resourceDomains: []
|
|
43
|
+
},
|
|
44
|
+
prefersBorder: true
|
|
45
|
+
},
|
|
46
|
+
"openai/widgetPrefersBorder": true,
|
|
47
|
+
"openai/widgetCSP": {
|
|
48
|
+
connect_domains: [],
|
|
49
|
+
resource_domains: []
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
read: () => html
|
|
53
|
+
};
|
|
54
|
+
}
|
|
29
55
|
const plugins = [];
|
|
30
56
|
for (const declaration of descriptor.entries) {
|
|
31
57
|
const eligible = declaration.owners.some((owner) => owner.majorMode === options.majorMode && (owner.layer === "default" || options.activeLayers.includes(owner.layer)));
|
|
@@ -35,9 +61,42 @@ async function loadMcpBundle(options) {
|
|
|
35
61
|
if (node_crypto.default.createHash("sha256").update(node_fs.default.readFileSync(file)).digest("hex") !== declaration.sha256) throw new Error("module hash does not match the admitted descriptor");
|
|
36
62
|
const imported = await import((0, node_url.pathToFileURL)(file).href);
|
|
37
63
|
if (!isMcpPlugin(imported.default)) throw new Error("default export is not an MCP plugin");
|
|
64
|
+
const original = imported.default;
|
|
65
|
+
const ui = resource;
|
|
66
|
+
const allowedWidgets = new Set(declaration.widgets ?? []);
|
|
67
|
+
const plugin = ui === void 0 || allowedWidgets.size === 0 ? original : {
|
|
68
|
+
...original,
|
|
69
|
+
async session(context) {
|
|
70
|
+
const { tools: contributedTools, uiResources: contributedResources, ...rest } = typeof original.session === "function" ? await original.session(context) : original.session;
|
|
71
|
+
let usesWidget = false;
|
|
72
|
+
const tools = contributedTools?.map((tool) => {
|
|
73
|
+
const widget = tool._meta?.["doompi/widget"];
|
|
74
|
+
if (widget === void 0) return tool;
|
|
75
|
+
if (typeof widget !== "string" || !allowedWidgets.has(widget)) throw new Error(`MCP tool '${tool.name}' references a widget not owned by its package`);
|
|
76
|
+
usesWidget = true;
|
|
77
|
+
return {
|
|
78
|
+
...tool,
|
|
79
|
+
_meta: {
|
|
80
|
+
...tool._meta,
|
|
81
|
+
ui: {
|
|
82
|
+
...tool._meta?.ui,
|
|
83
|
+
resourceUri: ui.uri,
|
|
84
|
+
visibility: tool._meta?.ui?.visibility ?? ["model"]
|
|
85
|
+
},
|
|
86
|
+
"openai/outputTemplate": ui.uri
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
...rest,
|
|
92
|
+
...tools === void 0 ? {} : { tools },
|
|
93
|
+
uiResources: [...contributedResources ?? [], ...usesWidget ? [ui] : []]
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
};
|
|
38
97
|
plugins.push({
|
|
39
98
|
declaration,
|
|
40
|
-
plugin
|
|
99
|
+
plugin
|
|
41
100
|
});
|
|
42
101
|
} catch (error) {
|
|
43
102
|
const message = `MCP plugin '${declaration.packageName}' could not load (${error instanceof Error ? error.message : String(error)})`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcpBundleLoader.cjs","names":["fs","path","DOOM_MCP_BUNDLE_FILE","crypto","parseDoomMcpBundle","pathToFileURL"],"sources":["../../../src/server/mcpBundleLoader.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport {\n DOOM_MCP_BUNDLE_FILE,\n type DoomMcpBundle,\n type DoomMcpBundleEntry,\n parseDoomMcpBundle,\n} from '../schemas/mcpBundle';\nimport type { DoomMcpPluginDefinition } from '../schemas/mcpFacet';\n\nexport interface LoadMcpBundleOptions {\n readonly directory: string;\n readonly generation: string;\n readonly fingerprint: string;\n /** SHA-256 of the descriptor bytes admitted by sync registration. */\n readonly descriptorSha256: string;\n readonly majorMode: string;\n readonly activeLayers: readonly string[];\n readonly onNotice?: (message: string) => void;\n /** Retain admitted candidates so a live session can follow selection changes. */\n readonly retainCandidates?: boolean;\n}\n\nexport interface LoadedMcpPlugin {\n readonly declaration: DoomMcpBundleEntry;\n readonly plugin: DoomMcpPluginDefinition;\n}\n\nexport interface LoadedMcpBundle {\n readonly descriptor: DoomMcpBundle;\n readonly plugins: readonly LoadedMcpPlugin[];\n}\n\nfunction containedFile(directory: string, relativeFile: string): string {\n const resolved = fs.realpathSync(path.resolve(directory, relativeFile));\n const relative = path.relative(directory, resolved);\n if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))\n throw new Error(`MCP bundle file escapes its generation: ${relativeFile}`);\n return resolved;\n}\n\nfunction isMcpPlugin(value: unknown): value is DoomMcpPluginDefinition {\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as Partial<DoomMcpPluginDefinition>;\n return (\n typeof candidate.name === 'string' &&\n (typeof candidate.session === 'function' || typeof candidate.session === 'object')\n );\n}\n\n/** Imports only modules named by an admitted MCP descriptor. */\nexport async function loadMcpBundle(options: LoadMcpBundleOptions): Promise<LoadedMcpBundle> {\n const directory = fs.realpathSync(options.directory);\n const descriptorBytes = fs.readFileSync(containedFile(directory, DOOM_MCP_BUNDLE_FILE));\n const descriptorSha256 = crypto.createHash('sha256').update(descriptorBytes).digest('hex');\n if (descriptorSha256 !== options.descriptorSha256)\n throw new Error('MCP descriptor hash does not match the admitted descriptor');\n const descriptor = parseDoomMcpBundle(JSON.parse(descriptorBytes.toString('utf8')));\n if (descriptor.generation !== options.generation || descriptor.fingerprint !== options.fingerprint)\n throw new Error('MCP bundle does not match the admitted generation');\n const plugins: LoadedMcpPlugin[] = [];\n for (const declaration of descriptor.entries) {\n const eligible = declaration.owners.some(\n (owner) =>\n owner.majorMode === options.majorMode &&\n (owner.layer === 'default' || options.activeLayers.includes(owner.layer)),\n );\n if (!eligible && options.retainCandidates !== true) continue;\n try {\n const file = containedFile(directory, declaration.module);\n const sha256 = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');\n if (sha256 !== declaration.sha256) throw new Error('module hash does not match the admitted descriptor');\n const imported = (await import(pathToFileURL(file).href)) as { default?: unknown };\n if (!isMcpPlugin(imported.default)) throw new Error('default export is not an MCP plugin');\n plugins.push({ declaration, plugin
|
|
1
|
+
{"version":3,"file":"mcpBundleLoader.cjs","names":["fs","path","DOOM_MCP_BUNDLE_FILE","crypto","parseDoomMcpBundle","pathToFileURL"],"sources":["../../../src/server/mcpBundleLoader.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport {\n DOOM_MCP_BUNDLE_FILE,\n type DoomMcpBundle,\n type DoomMcpBundleEntry,\n parseDoomMcpBundle,\n} from '../schemas/mcpBundle';\nimport type { DoomMcpPluginDefinition, DoomMcpUiResource } from '../schemas/mcpFacet';\n\nexport interface LoadMcpBundleOptions {\n readonly directory: string;\n readonly generation: string;\n readonly fingerprint: string;\n /** SHA-256 of the descriptor bytes admitted by sync registration. */\n readonly descriptorSha256: string;\n readonly majorMode: string;\n readonly activeLayers: readonly string[];\n readonly onNotice?: (message: string) => void;\n /** Retain admitted candidates so a live session can follow selection changes. */\n readonly retainCandidates?: boolean;\n}\n\nexport interface LoadedMcpPlugin {\n readonly declaration: DoomMcpBundleEntry;\n readonly plugin: DoomMcpPluginDefinition;\n}\n\nexport interface LoadedMcpBundle {\n readonly descriptor: DoomMcpBundle;\n readonly plugins: readonly LoadedMcpPlugin[];\n}\n\nfunction containedFile(directory: string, relativeFile: string): string {\n const resolved = fs.realpathSync(path.resolve(directory, relativeFile));\n const relative = path.relative(directory, resolved);\n if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))\n throw new Error(`MCP bundle file escapes its generation: ${relativeFile}`);\n return resolved;\n}\n\nfunction isMcpPlugin(value: unknown): value is DoomMcpPluginDefinition {\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as Partial<DoomMcpPluginDefinition>;\n return (\n typeof candidate.name === 'string' &&\n (typeof candidate.session === 'function' || typeof candidate.session === 'object')\n );\n}\n\n/** Imports only modules named by an admitted MCP descriptor. */\nexport async function loadMcpBundle(options: LoadMcpBundleOptions): Promise<LoadedMcpBundle> {\n const directory = fs.realpathSync(options.directory);\n const descriptorBytes = fs.readFileSync(containedFile(directory, DOOM_MCP_BUNDLE_FILE));\n const descriptorSha256 = crypto.createHash('sha256').update(descriptorBytes).digest('hex');\n if (descriptorSha256 !== options.descriptorSha256)\n throw new Error('MCP descriptor hash does not match the admitted descriptor');\n const descriptor = parseDoomMcpBundle(JSON.parse(descriptorBytes.toString('utf8')));\n if (descriptor.generation !== options.generation || descriptor.fingerprint !== options.fingerprint)\n throw new Error('MCP bundle does not match the admitted generation');\n let resource: DoomMcpUiResource | undefined;\n if (descriptor.ui) {\n const html = fs.readFileSync(containedFile(directory, descriptor.ui.file), 'utf8');\n if (crypto.createHash('sha256').update(html).digest('hex') !== descriptor.ui.sha256)\n throw new Error('MCP UI hash does not match the admitted descriptor');\n resource = {\n uri: `ui://doompi/tools/${descriptor.ui.sha256}/index.html`,\n name: 'doompi-tool-widgets',\n description: 'Package-owned tool widgets composed by doompi sync.',\n mimeType: 'text/html;profile=mcp-app',\n _meta: {\n ui: { csp: { connectDomains: [], resourceDomains: [] }, prefersBorder: true },\n 'openai/widgetPrefersBorder': true,\n 'openai/widgetCSP': { connect_domains: [], resource_domains: [] },\n },\n read: () => html,\n };\n }\n const plugins: LoadedMcpPlugin[] = [];\n for (const declaration of descriptor.entries) {\n const eligible = declaration.owners.some(\n (owner) =>\n owner.majorMode === options.majorMode &&\n (owner.layer === 'default' || options.activeLayers.includes(owner.layer)),\n );\n if (!eligible && options.retainCandidates !== true) continue;\n try {\n const file = containedFile(directory, declaration.module);\n const sha256 = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');\n if (sha256 !== declaration.sha256) throw new Error('module hash does not match the admitted descriptor');\n const imported = (await import(pathToFileURL(file).href)) as { default?: unknown };\n if (!isMcpPlugin(imported.default)) throw new Error('default export is not an MCP plugin');\n const original = imported.default;\n const ui = resource;\n const allowedWidgets = new Set(declaration.widgets ?? []);\n const plugin: DoomMcpPluginDefinition =\n ui === undefined || allowedWidgets.size === 0\n ? original\n : {\n ...original,\n async session(context) {\n const scope =\n typeof original.session === 'function' ? await original.session(context) : original.session;\n const { tools: contributedTools, uiResources: contributedResources, ...rest } = scope;\n let usesWidget = false;\n const tools = contributedTools?.map((tool) => {\n const widget = tool._meta?.['doompi/widget'];\n if (widget === undefined) return tool;\n if (typeof widget !== 'string' || !allowedWidgets.has(widget))\n throw new Error(`MCP tool '${tool.name}' references a widget not owned by its package`);\n usesWidget = true;\n return {\n ...tool,\n _meta: {\n ...tool._meta,\n ui: {\n ...tool._meta?.ui,\n resourceUri: ui.uri,\n visibility: tool._meta?.ui?.visibility ?? ['model'],\n },\n 'openai/outputTemplate': ui.uri,\n },\n };\n });\n return {\n ...rest,\n ...(tools === undefined ? {} : { tools }),\n uiResources: [...(contributedResources ?? []), ...(usesWidget ? [ui] : [])],\n };\n },\n };\n plugins.push({ declaration, plugin });\n } catch (error) {\n const message = `MCP plugin '${declaration.packageName}' could not load (${error instanceof Error ? error.message : String(error)})`;\n if (eligible) throw new Error(message, { cause: error });\n options.onNotice?.(message);\n }\n }\n return { descriptor, plugins };\n}\n"],"mappings":"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"}
|
|
@@ -22,6 +22,32 @@ async function loadMcpBundle(options) {
|
|
|
22
22
|
if (crypto.createHash("sha256").update(descriptorBytes).digest("hex") !== options.descriptorSha256) throw new Error("MCP descriptor hash does not match the admitted descriptor");
|
|
23
23
|
const descriptor = parseDoomMcpBundle(JSON.parse(descriptorBytes.toString("utf8")));
|
|
24
24
|
if (descriptor.generation !== options.generation || descriptor.fingerprint !== options.fingerprint) throw new Error("MCP bundle does not match the admitted generation");
|
|
25
|
+
let resource;
|
|
26
|
+
if (descriptor.ui) {
|
|
27
|
+
const html = fs.readFileSync(containedFile(directory, descriptor.ui.file), "utf8");
|
|
28
|
+
if (crypto.createHash("sha256").update(html).digest("hex") !== descriptor.ui.sha256) throw new Error("MCP UI hash does not match the admitted descriptor");
|
|
29
|
+
resource = {
|
|
30
|
+
uri: `ui://doompi/tools/${descriptor.ui.sha256}/index.html`,
|
|
31
|
+
name: "doompi-tool-widgets",
|
|
32
|
+
description: "Package-owned tool widgets composed by doompi sync.",
|
|
33
|
+
mimeType: "text/html;profile=mcp-app",
|
|
34
|
+
_meta: {
|
|
35
|
+
ui: {
|
|
36
|
+
csp: {
|
|
37
|
+
connectDomains: [],
|
|
38
|
+
resourceDomains: []
|
|
39
|
+
},
|
|
40
|
+
prefersBorder: true
|
|
41
|
+
},
|
|
42
|
+
"openai/widgetPrefersBorder": true,
|
|
43
|
+
"openai/widgetCSP": {
|
|
44
|
+
connect_domains: [],
|
|
45
|
+
resource_domains: []
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
read: () => html
|
|
49
|
+
};
|
|
50
|
+
}
|
|
25
51
|
const plugins = [];
|
|
26
52
|
for (const declaration of descriptor.entries) {
|
|
27
53
|
const eligible = declaration.owners.some((owner) => owner.majorMode === options.majorMode && (owner.layer === "default" || options.activeLayers.includes(owner.layer)));
|
|
@@ -31,9 +57,42 @@ async function loadMcpBundle(options) {
|
|
|
31
57
|
if (crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex") !== declaration.sha256) throw new Error("module hash does not match the admitted descriptor");
|
|
32
58
|
const imported = await import(pathToFileURL(file).href);
|
|
33
59
|
if (!isMcpPlugin(imported.default)) throw new Error("default export is not an MCP plugin");
|
|
60
|
+
const original = imported.default;
|
|
61
|
+
const ui = resource;
|
|
62
|
+
const allowedWidgets = new Set(declaration.widgets ?? []);
|
|
63
|
+
const plugin = ui === void 0 || allowedWidgets.size === 0 ? original : {
|
|
64
|
+
...original,
|
|
65
|
+
async session(context) {
|
|
66
|
+
const { tools: contributedTools, uiResources: contributedResources, ...rest } = typeof original.session === "function" ? await original.session(context) : original.session;
|
|
67
|
+
let usesWidget = false;
|
|
68
|
+
const tools = contributedTools?.map((tool) => {
|
|
69
|
+
const widget = tool._meta?.["doompi/widget"];
|
|
70
|
+
if (widget === void 0) return tool;
|
|
71
|
+
if (typeof widget !== "string" || !allowedWidgets.has(widget)) throw new Error(`MCP tool '${tool.name}' references a widget not owned by its package`);
|
|
72
|
+
usesWidget = true;
|
|
73
|
+
return {
|
|
74
|
+
...tool,
|
|
75
|
+
_meta: {
|
|
76
|
+
...tool._meta,
|
|
77
|
+
ui: {
|
|
78
|
+
...tool._meta?.ui,
|
|
79
|
+
resourceUri: ui.uri,
|
|
80
|
+
visibility: tool._meta?.ui?.visibility ?? ["model"]
|
|
81
|
+
},
|
|
82
|
+
"openai/outputTemplate": ui.uri
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
...rest,
|
|
88
|
+
...tools === void 0 ? {} : { tools },
|
|
89
|
+
uiResources: [...contributedResources ?? [], ...usesWidget ? [ui] : []]
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
};
|
|
34
93
|
plugins.push({
|
|
35
94
|
declaration,
|
|
36
|
-
plugin
|
|
95
|
+
plugin
|
|
37
96
|
});
|
|
38
97
|
} catch (error) {
|
|
39
98
|
const message = `MCP plugin '${declaration.packageName}' could not load (${error instanceof Error ? error.message : String(error)})`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcpBundleLoader.mjs","names":[],"sources":["../../../src/server/mcpBundleLoader.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport {\n DOOM_MCP_BUNDLE_FILE,\n type DoomMcpBundle,\n type DoomMcpBundleEntry,\n parseDoomMcpBundle,\n} from '../schemas/mcpBundle';\nimport type { DoomMcpPluginDefinition } from '../schemas/mcpFacet';\n\nexport interface LoadMcpBundleOptions {\n readonly directory: string;\n readonly generation: string;\n readonly fingerprint: string;\n /** SHA-256 of the descriptor bytes admitted by sync registration. */\n readonly descriptorSha256: string;\n readonly majorMode: string;\n readonly activeLayers: readonly string[];\n readonly onNotice?: (message: string) => void;\n /** Retain admitted candidates so a live session can follow selection changes. */\n readonly retainCandidates?: boolean;\n}\n\nexport interface LoadedMcpPlugin {\n readonly declaration: DoomMcpBundleEntry;\n readonly plugin: DoomMcpPluginDefinition;\n}\n\nexport interface LoadedMcpBundle {\n readonly descriptor: DoomMcpBundle;\n readonly plugins: readonly LoadedMcpPlugin[];\n}\n\nfunction containedFile(directory: string, relativeFile: string): string {\n const resolved = fs.realpathSync(path.resolve(directory, relativeFile));\n const relative = path.relative(directory, resolved);\n if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))\n throw new Error(`MCP bundle file escapes its generation: ${relativeFile}`);\n return resolved;\n}\n\nfunction isMcpPlugin(value: unknown): value is DoomMcpPluginDefinition {\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as Partial<DoomMcpPluginDefinition>;\n return (\n typeof candidate.name === 'string' &&\n (typeof candidate.session === 'function' || typeof candidate.session === 'object')\n );\n}\n\n/** Imports only modules named by an admitted MCP descriptor. */\nexport async function loadMcpBundle(options: LoadMcpBundleOptions): Promise<LoadedMcpBundle> {\n const directory = fs.realpathSync(options.directory);\n const descriptorBytes = fs.readFileSync(containedFile(directory, DOOM_MCP_BUNDLE_FILE));\n const descriptorSha256 = crypto.createHash('sha256').update(descriptorBytes).digest('hex');\n if (descriptorSha256 !== options.descriptorSha256)\n throw new Error('MCP descriptor hash does not match the admitted descriptor');\n const descriptor = parseDoomMcpBundle(JSON.parse(descriptorBytes.toString('utf8')));\n if (descriptor.generation !== options.generation || descriptor.fingerprint !== options.fingerprint)\n throw new Error('MCP bundle does not match the admitted generation');\n const plugins: LoadedMcpPlugin[] = [];\n for (const declaration of descriptor.entries) {\n const eligible = declaration.owners.some(\n (owner) =>\n owner.majorMode === options.majorMode &&\n (owner.layer === 'default' || options.activeLayers.includes(owner.layer)),\n );\n if (!eligible && options.retainCandidates !== true) continue;\n try {\n const file = containedFile(directory, declaration.module);\n const sha256 = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');\n if (sha256 !== declaration.sha256) throw new Error('module hash does not match the admitted descriptor');\n const imported = (await import(pathToFileURL(file).href)) as { default?: unknown };\n if (!isMcpPlugin(imported.default)) throw new Error('default export is not an MCP plugin');\n plugins.push({ declaration, plugin
|
|
1
|
+
{"version":3,"file":"mcpBundleLoader.mjs","names":[],"sources":["../../../src/server/mcpBundleLoader.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport {\n DOOM_MCP_BUNDLE_FILE,\n type DoomMcpBundle,\n type DoomMcpBundleEntry,\n parseDoomMcpBundle,\n} from '../schemas/mcpBundle';\nimport type { DoomMcpPluginDefinition, DoomMcpUiResource } from '../schemas/mcpFacet';\n\nexport interface LoadMcpBundleOptions {\n readonly directory: string;\n readonly generation: string;\n readonly fingerprint: string;\n /** SHA-256 of the descriptor bytes admitted by sync registration. */\n readonly descriptorSha256: string;\n readonly majorMode: string;\n readonly activeLayers: readonly string[];\n readonly onNotice?: (message: string) => void;\n /** Retain admitted candidates so a live session can follow selection changes. */\n readonly retainCandidates?: boolean;\n}\n\nexport interface LoadedMcpPlugin {\n readonly declaration: DoomMcpBundleEntry;\n readonly plugin: DoomMcpPluginDefinition;\n}\n\nexport interface LoadedMcpBundle {\n readonly descriptor: DoomMcpBundle;\n readonly plugins: readonly LoadedMcpPlugin[];\n}\n\nfunction containedFile(directory: string, relativeFile: string): string {\n const resolved = fs.realpathSync(path.resolve(directory, relativeFile));\n const relative = path.relative(directory, resolved);\n if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))\n throw new Error(`MCP bundle file escapes its generation: ${relativeFile}`);\n return resolved;\n}\n\nfunction isMcpPlugin(value: unknown): value is DoomMcpPluginDefinition {\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as Partial<DoomMcpPluginDefinition>;\n return (\n typeof candidate.name === 'string' &&\n (typeof candidate.session === 'function' || typeof candidate.session === 'object')\n );\n}\n\n/** Imports only modules named by an admitted MCP descriptor. */\nexport async function loadMcpBundle(options: LoadMcpBundleOptions): Promise<LoadedMcpBundle> {\n const directory = fs.realpathSync(options.directory);\n const descriptorBytes = fs.readFileSync(containedFile(directory, DOOM_MCP_BUNDLE_FILE));\n const descriptorSha256 = crypto.createHash('sha256').update(descriptorBytes).digest('hex');\n if (descriptorSha256 !== options.descriptorSha256)\n throw new Error('MCP descriptor hash does not match the admitted descriptor');\n const descriptor = parseDoomMcpBundle(JSON.parse(descriptorBytes.toString('utf8')));\n if (descriptor.generation !== options.generation || descriptor.fingerprint !== options.fingerprint)\n throw new Error('MCP bundle does not match the admitted generation');\n let resource: DoomMcpUiResource | undefined;\n if (descriptor.ui) {\n const html = fs.readFileSync(containedFile(directory, descriptor.ui.file), 'utf8');\n if (crypto.createHash('sha256').update(html).digest('hex') !== descriptor.ui.sha256)\n throw new Error('MCP UI hash does not match the admitted descriptor');\n resource = {\n uri: `ui://doompi/tools/${descriptor.ui.sha256}/index.html`,\n name: 'doompi-tool-widgets',\n description: 'Package-owned tool widgets composed by doompi sync.',\n mimeType: 'text/html;profile=mcp-app',\n _meta: {\n ui: { csp: { connectDomains: [], resourceDomains: [] }, prefersBorder: true },\n 'openai/widgetPrefersBorder': true,\n 'openai/widgetCSP': { connect_domains: [], resource_domains: [] },\n },\n read: () => html,\n };\n }\n const plugins: LoadedMcpPlugin[] = [];\n for (const declaration of descriptor.entries) {\n const eligible = declaration.owners.some(\n (owner) =>\n owner.majorMode === options.majorMode &&\n (owner.layer === 'default' || options.activeLayers.includes(owner.layer)),\n );\n if (!eligible && options.retainCandidates !== true) continue;\n try {\n const file = containedFile(directory, declaration.module);\n const sha256 = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');\n if (sha256 !== declaration.sha256) throw new Error('module hash does not match the admitted descriptor');\n const imported = (await import(pathToFileURL(file).href)) as { default?: unknown };\n if (!isMcpPlugin(imported.default)) throw new Error('default export is not an MCP plugin');\n const original = imported.default;\n const ui = resource;\n const allowedWidgets = new Set(declaration.widgets ?? []);\n const plugin: DoomMcpPluginDefinition =\n ui === undefined || allowedWidgets.size === 0\n ? original\n : {\n ...original,\n async session(context) {\n const scope =\n typeof original.session === 'function' ? await original.session(context) : original.session;\n const { tools: contributedTools, uiResources: contributedResources, ...rest } = scope;\n let usesWidget = false;\n const tools = contributedTools?.map((tool) => {\n const widget = tool._meta?.['doompi/widget'];\n if (widget === undefined) return tool;\n if (typeof widget !== 'string' || !allowedWidgets.has(widget))\n throw new Error(`MCP tool '${tool.name}' references a widget not owned by its package`);\n usesWidget = true;\n return {\n ...tool,\n _meta: {\n ...tool._meta,\n ui: {\n ...tool._meta?.ui,\n resourceUri: ui.uri,\n visibility: tool._meta?.ui?.visibility ?? ['model'],\n },\n 'openai/outputTemplate': ui.uri,\n },\n };\n });\n return {\n ...rest,\n ...(tools === undefined ? {} : { tools }),\n uiResources: [...(contributedResources ?? []), ...(usesWidget ? [ui] : [])],\n };\n },\n };\n plugins.push({ declaration, plugin });\n } catch (error) {\n const message = `MCP plugin '${declaration.packageName}' could not load (${error instanceof Error ? error.message : String(error)})`;\n if (eligible) throw new Error(message, { cause: error });\n options.onNotice?.(message);\n }\n }\n return { descriptor, plugins };\n}\n"],"mappings":"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"}
|
|
@@ -46,6 +46,18 @@ function grantedSurface(grant, surface) {
|
|
|
46
46
|
uiResources: (snapshot.uiResources ?? []).filter((resource) => resourceUris.has(resource.uri))
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
+
/** Identity is supplied by the approved tool descriptor, never by upstream result metadata. */
|
|
50
|
+
function widgetResult(tool, result) {
|
|
51
|
+
const widget = tool?._meta?.["doompi/widget"];
|
|
52
|
+
return typeof widget !== "string" ? result : {
|
|
53
|
+
...result,
|
|
54
|
+
_meta: {
|
|
55
|
+
...result._meta,
|
|
56
|
+
"doompi/widget": widget,
|
|
57
|
+
"doompi/toolName": tool.name
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
49
61
|
/** Creates a stateless Streamable HTTP MCP request handler for session capabilities. */
|
|
50
62
|
function createSessionMcpHttpHandler(options) {
|
|
51
63
|
const audience = new URL(options.audience);
|
|
@@ -141,10 +153,12 @@ function createSessionMcpHttpHandler(options) {
|
|
|
141
153
|
]);
|
|
142
154
|
const invocationId = (0, node_crypto.randomUUID)();
|
|
143
155
|
let skillAccessOpen = true;
|
|
156
|
+
let widgetTool;
|
|
144
157
|
try {
|
|
145
158
|
const parent = await authorizeOperation();
|
|
146
159
|
const advertised = grantedSurface(parent.grant, parent.target.toolSurface).tools.find((tool) => tool.name === message.params.name);
|
|
147
160
|
if (!advertised) throw new _modelcontextprotocol_sdk_types_js.McpError(_modelcontextprotocol_sdk_types_js.ErrorCode.InvalidParams, `Tool '${message.params.name}' is not granted or active.`);
|
|
161
|
+
widgetTool = advertised;
|
|
148
162
|
await options.onVerified?.(parent.grant);
|
|
149
163
|
options.onNotice?.(`session MCP invocation id=${invocationId} lifecycle=started`);
|
|
150
164
|
const resolveTarget = async (reserve) => {
|
|
@@ -207,16 +221,16 @@ function createSessionMcpHttpHandler(options) {
|
|
|
207
221
|
});
|
|
208
222
|
await recheck();
|
|
209
223
|
options.onNotice?.(`session MCP invocation id=${invocationId} lifecycle=succeeded`);
|
|
210
|
-
return {
|
|
224
|
+
return widgetResult(selected, {
|
|
211
225
|
content: result.content,
|
|
212
226
|
...result.structuredContent === void 0 ? {} : { structuredContent: result.structuredContent },
|
|
213
227
|
...result._meta === void 0 ? {} : { _meta: result._meta },
|
|
214
228
|
isError: result.isError ?? false
|
|
215
|
-
};
|
|
229
|
+
});
|
|
216
230
|
} catch (error) {
|
|
217
231
|
options.onNotice?.(`session MCP invocation id=${invocationId} lifecycle=failed`);
|
|
218
232
|
if (!(error instanceof require_index.SessionMcpConversationError)) throw error;
|
|
219
|
-
return {
|
|
233
|
+
return widgetResult(widgetTool, {
|
|
220
234
|
content: [{
|
|
221
235
|
type: "text",
|
|
222
236
|
text: error.message
|
|
@@ -227,7 +241,7 @@ function createSessionMcpHttpHandler(options) {
|
|
|
227
241
|
message: error.message,
|
|
228
242
|
...error.bindingId === void 0 ? {} : { bindingId: error.bindingId }
|
|
229
243
|
}
|
|
230
|
-
};
|
|
244
|
+
});
|
|
231
245
|
} finally {
|
|
232
246
|
skillAccessOpen = false;
|
|
233
247
|
operations.delete(key);
|