@eclipse-docks/extension-webmcp 0.7.122 → 0.7.124

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.
@@ -0,0 +1,2 @@
1
+ export declare function executeCommandTool(commandId: string, inputObject: unknown, signal?: AbortSignal): Promise<unknown>;
2
+ //# sourceMappingURL=execute-command-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute-command-tool.d.ts","sourceRoot":"","sources":["../src/execute-command-tool.ts"],"names":[],"mappings":"AAiEA,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,OAAO,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAsBlB"}
@@ -0,0 +1,4 @@
1
+ import { ModelContext } from './model-context';
2
+ export declare function getModelContext(): ModelContext | undefined;
3
+ export declare function isModelContextAvailable(): boolean;
4
+ //# sourceMappingURL=get-model-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-model-context.d.ts","sourceRoot":"","sources":["../src/get-model-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,wBAAgB,eAAe,IAAI,YAAY,GAAG,SAAS,CAa1D;AAED,wBAAgB,uBAAuB,IAAI,OAAO,CAEjD"}
package/dist/index.js CHANGED
@@ -1,17 +1,11 @@
1
1
  import { extensionRegistry } from "@eclipse-docks/core";
2
2
  import pkg from "../package.json";
3
3
  //#region src/index.ts
4
- function isModelContextAvailable() {
5
- return typeof navigator !== "undefined" && "modelContext" in navigator && navigator.modelContext != null;
6
- }
7
4
  extensionRegistry.registerExtension({
8
5
  id: pkg.name,
9
6
  name: "WebMCP",
10
7
  description: "Exposes app commands as WebMCP tools for browser agents and MCP clients",
11
- loader: async () => {
12
- if (!isModelContextAvailable()) throw new Error("WebMCP extension requires navigator.modelContext (Web Model Context API).");
13
- return import("./webmcp-extension-vZl42m8K.js");
14
- },
8
+ loader: () => import("./webmcp-extension-C9QPV2tP.js"),
15
9
  icon: "plug",
16
10
  dependencies: ["@eclipse-docks/extension-ai-system"],
17
11
  experimental: true
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { extensionRegistry } from \"@eclipse-docks/core\";\nimport pkg from \"../package.json\";\n\nfunction isModelContextAvailable(): boolean {\n return typeof navigator !== \"undefined\" && \"modelContext\" in navigator && navigator.modelContext != null;\n}\n\nextensionRegistry.registerExtension({\n id: pkg.name,\n name: \"WebMCP\",\n description: \"Exposes app commands as WebMCP tools for browser agents and MCP clients\",\n loader: async () => {\n if (!isModelContextAvailable()) {\n throw new Error(\"WebMCP extension requires navigator.modelContext (Web Model Context API).\");\n }\n return import(\"./webmcp-extension\");\n },\n icon: \"plug\",\n dependencies: [\"@eclipse-docks/extension-ai-system\"],\n experimental: true,\n});\n"],"mappings":";;;AAGA,SAAS,0BAAmC;AAC1C,QAAO,OAAO,cAAc,eAAe,kBAAkB,aAAa,UAAU,gBAAgB;;AAGtG,kBAAkB,kBAAkB;CAClC,IAAI,IAAI;CACR,MAAM;CACN,aAAa;CACb,QAAQ,YAAY;AAClB,MAAI,CAAC,yBAAyB,CAC5B,OAAM,IAAI,MAAM,4EAA4E;AAE9F,SAAO,OAAO;;CAEhB,MAAM;CACN,cAAc,CAAC,qCAAqC;CACpD,cAAc;CACf,CAAC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { extensionRegistry } from \"@eclipse-docks/core\";\nimport pkg from \"../package.json\";\n\nextensionRegistry.registerExtension({\n id: pkg.name,\n name: \"WebMCP\",\n description: \"Exposes app commands as WebMCP tools for browser agents and MCP clients\",\n loader: () => import(\"./webmcp-extension\"),\n icon: \"plug\",\n dependencies: [\"@eclipse-docks/extension-ai-system\"],\n experimental: true,\n});\n"],"mappings":";;;AAGA,kBAAkB,kBAAkB;CAClC,IAAI,IAAI;CACR,MAAM;CACN,aAAa;CACb,cAAc,OAAO;CACrB,MAAM;CACN,cAAc,CAAC,qCAAqC;CACpD,cAAc;CACf,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function sanitizeWebMcpToolName(name: string): string;
2
+ //# sourceMappingURL=sanitize-tool-name.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize-tool-name.d.ts","sourceRoot":"","sources":["../src/sanitize-tool-name.ts"],"names":[],"mappings":"AAEA,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgB3D"}
@@ -0,0 +1,203 @@
1
+ import { TOPIC_COMMAND_REGISTERED, commandRegistry, createLogger, subscribe, unsubscribe } from "@eclipse-docks/core";
2
+ import { ToolRegistry, sanitizeFunctionName } from "@eclipse-docks/extension-ai-system/api";
3
+ //#region src/execute-command-tool.ts
4
+ var logger$1 = createLogger("WebMCP");
5
+ function normalizeToolInput(inputObject) {
6
+ if (inputObject == null) return {};
7
+ if (typeof inputObject === "string") try {
8
+ return normalizeToolInput(JSON.parse(inputObject));
9
+ } catch {
10
+ return {};
11
+ }
12
+ if (typeof inputObject !== "object" || Array.isArray(inputObject)) return {};
13
+ const record = inputObject;
14
+ if ("params" in record && record.params != null && typeof record.params === "object" && !Array.isArray(record.params)) return record.params;
15
+ return record;
16
+ }
17
+ function sanitizeArguments(args, command) {
18
+ if (!command?.parameters?.length) return args;
19
+ const sanitizedArgs = {};
20
+ for (const param of command.parameters) {
21
+ const sanitizedParamName = sanitizeFunctionName(param.name);
22
+ if (sanitizedParamName in args) {
23
+ sanitizedArgs[param.name] = args[sanitizedParamName];
24
+ continue;
25
+ }
26
+ if (param.name in args) sanitizedArgs[param.name] = args[param.name];
27
+ }
28
+ return sanitizedArgs;
29
+ }
30
+ function formatCommandResult(result) {
31
+ if (result === void 0 || result === null) return "Done";
32
+ return result;
33
+ }
34
+ async function executeCommandTool(commandId, inputObject, signal) {
35
+ if (signal?.aborted) throw signal.reason ?? new DOMException("The tool execution was aborted.", "AbortError");
36
+ const command = commandRegistry.getCommand(commandId);
37
+ const args = sanitizeArguments(normalizeToolInput(inputObject), command);
38
+ const execContext = commandRegistry.createExecutionContext(args);
39
+ try {
40
+ const result = await commandRegistry.execute(commandId, execContext);
41
+ if (signal?.aborted) throw signal.reason ?? new DOMException("The tool execution was aborted.", "AbortError");
42
+ return formatCommandResult(result);
43
+ } catch (error) {
44
+ const message = error instanceof Error ? error.message : String(error);
45
+ logger$1.error(`Command "${commandId}" failed: ${message}`);
46
+ throw error instanceof Error ? error : new Error(message);
47
+ }
48
+ }
49
+ //#endregion
50
+ //#region src/get-model-context.ts
51
+ function getModelContext() {
52
+ if (typeof document !== "undefined") {
53
+ const { modelContext } = document;
54
+ if (modelContext) return modelContext;
55
+ }
56
+ if (typeof navigator !== "undefined" && navigator.modelContext) return navigator.modelContext;
57
+ }
58
+ //#endregion
59
+ //#region src/sanitize-tool-name.ts
60
+ var WEBMCP_TOOL_NAME_MAX_LENGTH = 128;
61
+ function sanitizeWebMcpToolName(name) {
62
+ let sanitized = name.replace(/[^a-zA-Z0-9_.-]/g, "_").replace(/^[^a-zA-Z]/, "cmd_$&").replace(/_+/g, "_").replace(/^_|_$/g, "");
63
+ if (!sanitized) sanitized = "tool";
64
+ if (sanitized.length > WEBMCP_TOOL_NAME_MAX_LENGTH) sanitized = sanitized.slice(0, WEBMCP_TOOL_NAME_MAX_LENGTH);
65
+ return sanitized;
66
+ }
67
+ //#endregion
68
+ //#region src/webmcp-service.ts
69
+ var logger = createLogger("WebMCP");
70
+ var READ_ONLY_COMMAND_IDS = new Set([
71
+ "ls",
72
+ "cat",
73
+ "head",
74
+ "tail",
75
+ "exists",
76
+ "wc",
77
+ "version-info",
78
+ "js"
79
+ ]);
80
+ var APP_LOADED_EVENT = "app-loaded";
81
+ function resolveCommandText(value, fallback) {
82
+ if (value == null) return fallback;
83
+ return (typeof value === "function" ? String(value()) : String(value)).trim() || fallback;
84
+ }
85
+ function toolDescription(command, toolDef) {
86
+ return resolveCommandText(toolDef.function.description ?? command.description ?? command.name ?? command.id, command.id);
87
+ }
88
+ var WebMCPService = class {
89
+ constructor() {
90
+ this.toolRegistry = new ToolRegistry();
91
+ this.registrationControllers = /* @__PURE__ */ new Map();
92
+ this.registeredIds = /* @__PURE__ */ new Set();
93
+ this.registeredToolNames = /* @__PURE__ */ new Set();
94
+ this.pendingRegistrations = /* @__PURE__ */ new Set();
95
+ this.commandSubscriptionToken = null;
96
+ this.appLoadedListener = null;
97
+ }
98
+ async start() {
99
+ if (!getModelContext()) {
100
+ logger.info("Web Model Context API not available in this browser.");
101
+ return;
102
+ }
103
+ this.commandSubscriptionToken = subscribe(TOPIC_COMMAND_REGISTERED, (command) => {
104
+ this.registerCommand(command);
105
+ });
106
+ this.appLoadedListener = () => {
107
+ this.syncAllCommands("app-loaded");
108
+ };
109
+ window.addEventListener(APP_LOADED_EVENT, this.appLoadedListener, { once: true });
110
+ await this.syncAllCommands("startup");
111
+ }
112
+ stop() {
113
+ if (this.appLoadedListener) {
114
+ window.removeEventListener(APP_LOADED_EVENT, this.appLoadedListener);
115
+ this.appLoadedListener = null;
116
+ }
117
+ if (this.commandSubscriptionToken !== null) {
118
+ unsubscribe(this.commandSubscriptionToken);
119
+ this.commandSubscriptionToken = null;
120
+ }
121
+ for (const controller of this.registrationControllers.values()) controller.abort();
122
+ this.registrationControllers.clear();
123
+ this.registeredIds.clear();
124
+ this.registeredToolNames.clear();
125
+ this.pendingRegistrations.clear();
126
+ }
127
+ toolDefToInputSchema(params) {
128
+ return {
129
+ type: "object",
130
+ properties: params.properties ?? {},
131
+ ...params.required?.length ? { required: params.required } : {}
132
+ };
133
+ }
134
+ isReadOnlyCommand(command) {
135
+ return READ_ONLY_COMMAND_IDS.has(command.id);
136
+ }
137
+ async syncAllCommands(reason) {
138
+ const commands = Object.values(commandRegistry.listCommands());
139
+ const registeredCount = (await Promise.all(commands.map((command) => this.registerCommand(command)))).filter(Boolean).length;
140
+ logger.info(`Synced ${registeredCount}/${commands.length} command(s) as WebMCP tools (${reason}).`);
141
+ const modelContext = getModelContext();
142
+ if (modelContext?.getTools) try {
143
+ const tools = await modelContext.getTools();
144
+ logger.info(`document.modelContext.getTools() reports ${tools.length} tool(s).`);
145
+ } catch (error) {
146
+ logger.warn("document.modelContext.getTools() failed:", error);
147
+ }
148
+ }
149
+ async registerCommand(command) {
150
+ if (this.registeredIds.has(command.id) || this.pendingRegistrations.has(command.id)) return false;
151
+ const modelContext = getModelContext();
152
+ if (!modelContext) return false;
153
+ const toolName = sanitizeWebMcpToolName(command.id);
154
+ if (this.registeredToolNames.has(toolName)) {
155
+ logger.warn(`Skipping command "${command.id}": WebMCP tool name "${toolName}" is already registered.`);
156
+ return false;
157
+ }
158
+ this.pendingRegistrations.add(command.id);
159
+ const schemaContext = commandRegistry.createExecutionContext?.() ?? {};
160
+ const toolDef = this.toolRegistry.commandToTool(command, schemaContext);
161
+ const commandId = command.id;
162
+ const registrationController = new AbortController();
163
+ try {
164
+ await modelContext.registerTool({
165
+ name: toolName,
166
+ title: resolveCommandText(command.name, command.id),
167
+ description: toolDescription(command, toolDef),
168
+ inputSchema: this.toolDefToInputSchema(toolDef.function.parameters),
169
+ annotations: { readOnlyHint: this.isReadOnlyCommand(command) },
170
+ async execute(inputObject, options) {
171
+ return executeCommandTool(commandId, inputObject, options?.signal);
172
+ }
173
+ }, { signal: registrationController.signal });
174
+ this.registrationControllers.set(toolName, registrationController);
175
+ this.registeredIds.add(command.id);
176
+ this.registeredToolNames.add(toolName);
177
+ return true;
178
+ } catch (error) {
179
+ registrationController.abort();
180
+ if (error instanceof DOMException && error.name === "InvalidStateError") {
181
+ this.registeredIds.add(command.id);
182
+ this.registeredToolNames.add(toolName);
183
+ return true;
184
+ }
185
+ logger.warn(`Failed to register WebMCP tool "${toolName}" for command "${commandId}":`, error);
186
+ return false;
187
+ } finally {
188
+ this.pendingRegistrations.delete(command.id);
189
+ }
190
+ }
191
+ };
192
+ //#endregion
193
+ //#region src/webmcp-extension.ts
194
+ var activeService = null;
195
+ var webmcp_extension_default = async (_uiContext) => {
196
+ if (activeService) activeService.stop();
197
+ activeService = new WebMCPService();
198
+ await activeService.start();
199
+ };
200
+ //#endregion
201
+ export { webmcp_extension_default as default };
202
+
203
+ //# sourceMappingURL=webmcp-extension-C9QPV2tP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp-extension-C9QPV2tP.js","names":[],"sources":["../src/execute-command-tool.ts","../src/get-model-context.ts","../src/sanitize-tool-name.ts","../src/webmcp-service.ts","../src/webmcp-extension.ts"],"sourcesContent":["import { commandRegistry, createLogger, type Command } from \"@eclipse-docks/core\";\nimport { sanitizeFunctionName } from \"@eclipse-docks/extension-ai-system/api\";\n\nconst logger = createLogger(\"WebMCP\");\n\nfunction normalizeToolInput(inputObject: unknown): Record<string, unknown> {\n if (inputObject == null) {\n return {};\n }\n\n if (typeof inputObject === \"string\") {\n try {\n const parsed: unknown = JSON.parse(inputObject);\n return normalizeToolInput(parsed);\n } catch {\n return {};\n }\n }\n\n if (typeof inputObject !== \"object\" || Array.isArray(inputObject)) {\n return {};\n }\n\n const record = inputObject as Record<string, unknown>;\n if (\n \"params\" in record &&\n record.params != null &&\n typeof record.params === \"object\" &&\n !Array.isArray(record.params)\n ) {\n return record.params as Record<string, unknown>;\n }\n\n return record;\n}\n\nfunction sanitizeArguments(\n args: Record<string, unknown>,\n command: Command | undefined\n): Record<string, unknown> {\n if (!command?.parameters?.length) {\n return args;\n }\n\n const sanitizedArgs: Record<string, unknown> = {};\n for (const param of command.parameters) {\n const sanitizedParamName = sanitizeFunctionName(param.name);\n if (sanitizedParamName in args) {\n sanitizedArgs[param.name] = args[sanitizedParamName];\n continue;\n }\n if (param.name in args) {\n sanitizedArgs[param.name] = args[param.name];\n }\n }\n return sanitizedArgs;\n}\n\nfunction formatCommandResult(result: unknown): unknown {\n if (result === undefined || result === null) {\n return \"Done\";\n }\n return result;\n}\n\nexport async function executeCommandTool(\n commandId: string,\n inputObject: unknown,\n signal?: AbortSignal\n): Promise<unknown> {\n if (signal?.aborted) {\n throw signal.reason ?? new DOMException(\"The tool execution was aborted.\", \"AbortError\");\n }\n\n const command = commandRegistry.getCommand(commandId);\n const args = sanitizeArguments(normalizeToolInput(inputObject), command);\n const execContext = commandRegistry.createExecutionContext(args);\n\n try {\n const result = await commandRegistry.execute(commandId, execContext);\n\n if (signal?.aborted) {\n throw signal.reason ?? new DOMException(\"The tool execution was aborted.\", \"AbortError\");\n }\n\n return formatCommandResult(result);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logger.error(`Command \"${commandId}\" failed: ${message}`);\n throw error instanceof Error ? error : new Error(message);\n }\n}\n","import type { ModelContext } from \"./model-context\";\n\nexport function getModelContext(): ModelContext | undefined {\n if (typeof document !== \"undefined\") {\n const { modelContext } = document;\n if (modelContext) {\n return modelContext;\n }\n }\n\n if (typeof navigator !== \"undefined\" && navigator.modelContext) {\n return navigator.modelContext;\n }\n\n return undefined;\n}\n\nexport function isModelContextAvailable(): boolean {\n return getModelContext() !== undefined;\n}\n","const WEBMCP_TOOL_NAME_MAX_LENGTH = 128;\n\nexport function sanitizeWebMcpToolName(name: string): string {\n let sanitized = name\n .replace(/[^a-zA-Z0-9_.-]/g, \"_\")\n .replace(/^[^a-zA-Z]/, \"cmd_$&\")\n .replace(/_+/g, \"_\")\n .replace(/^_|_$/g, \"\");\n\n if (!sanitized) {\n sanitized = \"tool\";\n }\n\n if (sanitized.length > WEBMCP_TOOL_NAME_MAX_LENGTH) {\n sanitized = sanitized.slice(0, WEBMCP_TOOL_NAME_MAX_LENGTH);\n }\n\n return sanitized;\n}\n","import {\n commandRegistry,\n createLogger,\n subscribe,\n unsubscribe,\n TOPIC_COMMAND_REGISTERED,\n} from \"@eclipse-docks/core\";\nimport { ToolRegistry, type ToolDefinition } from \"@eclipse-docks/extension-ai-system/api\";\nimport type { Command } from \"@eclipse-docks/core\";\nimport { executeCommandTool } from \"./execute-command-tool\";\nimport { getModelContext } from \"./get-model-context\";\nimport { sanitizeWebMcpToolName } from \"./sanitize-tool-name\";\n\nconst logger = createLogger(\"WebMCP\");\n\nconst READ_ONLY_COMMAND_IDS = new Set([\n \"ls\",\n \"cat\",\n \"head\",\n \"tail\",\n \"exists\",\n \"wc\",\n \"version-info\",\n \"js\",\n]);\n\nconst APP_LOADED_EVENT = \"app-loaded\";\n\nfunction resolveCommandText(value: unknown, fallback: string): string {\n if (value == null) return fallback;\n const text = typeof value === 'function' ? String(value()) : String(value);\n return text.trim() || fallback;\n}\n\nfunction toolDescription(command: Command, toolDef: ToolDefinition): string {\n return resolveCommandText(\n toolDef.function.description ?? command.description ?? command.name ?? command.id,\n command.id,\n );\n}\n\nexport class WebMCPService {\n private readonly toolRegistry = new ToolRegistry();\n private readonly registrationControllers = new Map<string, AbortController>();\n private readonly registeredIds = new Set<string>();\n private readonly registeredToolNames = new Set<string>();\n private readonly pendingRegistrations = new Set<string>();\n private commandSubscriptionToken: ReturnType<typeof subscribe> | null = null;\n private appLoadedListener: (() => void) | null = null;\n\n async start(): Promise<void> {\n if (!getModelContext()) {\n logger.info(\"Web Model Context API not available in this browser.\");\n return;\n }\n\n this.commandSubscriptionToken = subscribe(TOPIC_COMMAND_REGISTERED, (command: Command) => {\n void this.registerCommand(command);\n });\n\n this.appLoadedListener = () => {\n void this.syncAllCommands(\"app-loaded\");\n };\n window.addEventListener(APP_LOADED_EVENT, this.appLoadedListener, { once: true });\n\n await this.syncAllCommands(\"startup\");\n }\n\n stop(): void {\n if (this.appLoadedListener) {\n window.removeEventListener(APP_LOADED_EVENT, this.appLoadedListener);\n this.appLoadedListener = null;\n }\n\n if (this.commandSubscriptionToken !== null) {\n unsubscribe(this.commandSubscriptionToken);\n this.commandSubscriptionToken = null;\n }\n\n for (const controller of this.registrationControllers.values()) {\n controller.abort();\n }\n this.registrationControllers.clear();\n this.registeredIds.clear();\n this.registeredToolNames.clear();\n this.pendingRegistrations.clear();\n }\n\n private toolDefToInputSchema(\n params: ToolDefinition[\"function\"][\"parameters\"]\n ): Record<string, unknown> {\n return {\n type: \"object\",\n properties: params.properties ?? {},\n ...(params.required?.length ? { required: params.required } : {}),\n };\n }\n\n private isReadOnlyCommand(command: Command): boolean {\n return READ_ONLY_COMMAND_IDS.has(command.id);\n }\n\n private async syncAllCommands(reason: string): Promise<void> {\n const commands = Object.values(commandRegistry.listCommands()) as Command[];\n const results = await Promise.all(commands.map((command) => this.registerCommand(command)));\n const registeredCount = results.filter(Boolean).length;\n logger.info(`Synced ${registeredCount}/${commands.length} command(s) as WebMCP tools (${reason}).`);\n\n const modelContext = getModelContext();\n if (modelContext?.getTools) {\n try {\n const tools = await modelContext.getTools();\n logger.info(`document.modelContext.getTools() reports ${tools.length} tool(s).`);\n } catch (error) {\n logger.warn(\"document.modelContext.getTools() failed:\", error);\n }\n }\n }\n\n private async registerCommand(command: Command): Promise<boolean> {\n if (this.registeredIds.has(command.id) || this.pendingRegistrations.has(command.id)) {\n return false;\n }\n\n const modelContext = getModelContext();\n if (!modelContext) return false;\n\n const toolName = sanitizeWebMcpToolName(command.id);\n if (this.registeredToolNames.has(toolName)) {\n logger.warn(`Skipping command \"${command.id}\": WebMCP tool name \"${toolName}\" is already registered.`);\n return false;\n }\n\n this.pendingRegistrations.add(command.id);\n\n const schemaContext = commandRegistry.createExecutionContext?.() ?? {};\n const toolDef = this.toolRegistry.commandToTool(command, schemaContext) as ToolDefinition;\n const commandId = command.id;\n\n const registrationController = new AbortController();\n\n try {\n await modelContext.registerTool(\n {\n name: toolName,\n title: resolveCommandText(command.name, command.id),\n description: toolDescription(command, toolDef),\n inputSchema: this.toolDefToInputSchema(toolDef.function.parameters),\n annotations: {\n readOnlyHint: this.isReadOnlyCommand(command),\n },\n async execute(inputObject, options?) {\n return executeCommandTool(commandId, inputObject, options?.signal);\n },\n },\n { signal: registrationController.signal }\n );\n\n this.registrationControllers.set(toolName, registrationController);\n this.registeredIds.add(command.id);\n this.registeredToolNames.add(toolName);\n return true;\n } catch (error) {\n registrationController.abort();\n if (error instanceof DOMException && error.name === 'InvalidStateError') {\n this.registeredIds.add(command.id);\n this.registeredToolNames.add(toolName);\n return true;\n }\n logger.warn(`Failed to register WebMCP tool \"${toolName}\" for command \"${commandId}\":`, error);\n return false;\n } finally {\n this.pendingRegistrations.delete(command.id);\n }\n }\n}\n","import { WebMCPService } from \"./webmcp-service\";\n\nlet activeService: WebMCPService | null = null;\n\nexport default async (_uiContext: unknown): Promise<void> => {\n if (activeService) {\n activeService.stop();\n }\n\n activeService = new WebMCPService();\n await activeService.start();\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"webmcp-extension.d.ts","sourceRoot":"","sources":["../src/webmcp-extension.ts"],"names":[],"mappings":"yBAEsB,YAAY,OAAO,KAAG,OAAO,CAAC,IAAI,CAAC;AAAzD,wBAGE"}
1
+ {"version":3,"file":"webmcp-extension.d.ts","sourceRoot":"","sources":["../src/webmcp-extension.ts"],"names":[],"mappings":"yBAIsB,YAAY,OAAO,KAAG,OAAO,CAAC,IAAI,CAAC;AAAzD,wBAOE"}
@@ -1,12 +1,16 @@
1
1
  export declare class WebMCPService {
2
2
  private readonly toolRegistry;
3
- private readonly registeredNames;
3
+ private readonly registrationControllers;
4
4
  private readonly registeredIds;
5
+ private readonly registeredToolNames;
6
+ private readonly pendingRegistrations;
5
7
  private commandSubscriptionToken;
8
+ private appLoadedListener;
6
9
  start(): Promise<void>;
7
10
  stop(): void;
8
11
  private toolDefToInputSchema;
9
- private textContent;
12
+ private isReadOnlyCommand;
13
+ private syncAllCommands;
10
14
  private registerCommand;
11
15
  }
12
16
  //# sourceMappingURL=webmcp-service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"webmcp-service.d.ts","sourceRoot":"","sources":["../src/webmcp-service.ts"],"names":[],"mappings":"AASA,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IACnD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IACrD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,wBAAwB,CAA6C;IAEvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B,IAAI,IAAI,IAAI;IAaZ,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,eAAe;CAsCxB"}
1
+ {"version":3,"file":"webmcp-service.d.ts","sourceRoot":"","sources":["../src/webmcp-service.ts"],"names":[],"mappings":"AAyCA,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IACnD,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAsC;IAC9E,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IACzD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAqB;IAC1D,OAAO,CAAC,wBAAwB,CAA6C;IAC7E,OAAO,CAAC,iBAAiB,CAA6B;IAEhD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB5B,IAAI,IAAI,IAAI;IAoBZ,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,iBAAiB;YAIX,eAAe;YAiBf,eAAe;CAwD9B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eclipse-docks/extension-webmcp",
3
- "version": "0.7.122",
3
+ "version": "0.7.124",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -1,76 +0,0 @@
1
- import { TOPIC_COMMAND_REGISTERED, commandRegistry, subscribe, unsubscribe } from "@eclipse-docks/core";
2
- import { ToolRegistry } from "@eclipse-docks/extension-ai-system/api";
3
- //#region src/webmcp-service.ts
4
- var WebMCPService = class {
5
- constructor() {
6
- this.toolRegistry = new ToolRegistry();
7
- this.registeredNames = /* @__PURE__ */ new Set();
8
- this.registeredIds = /* @__PURE__ */ new Set();
9
- this.commandSubscriptionToken = null;
10
- }
11
- async start() {
12
- const initialCommands = Object.values(commandRegistry.listCommands());
13
- for (const command of initialCommands) this.registerCommand(command);
14
- this.commandSubscriptionToken = subscribe(TOPIC_COMMAND_REGISTERED, (command) => {
15
- this.registerCommand(command);
16
- });
17
- }
18
- stop() {
19
- if (this.commandSubscriptionToken !== null) {
20
- unsubscribe(this.commandSubscriptionToken);
21
- this.commandSubscriptionToken = null;
22
- }
23
- const modelContext = navigator.modelContext;
24
- if (modelContext) this.registeredNames.forEach((name) => modelContext.unregisterTool(name));
25
- this.registeredNames.clear();
26
- this.registeredIds.clear();
27
- }
28
- toolDefToInputSchema(params) {
29
- return {
30
- type: "object",
31
- properties: params.properties,
32
- required: params.required
33
- };
34
- }
35
- textContent(text) {
36
- return { content: [{
37
- type: "text",
38
- text
39
- }] };
40
- }
41
- registerCommand(command) {
42
- if (this.registeredIds.has(command.id)) return;
43
- const modelContext = navigator.modelContext;
44
- if (!modelContext) return;
45
- const schemaContext = commandRegistry.createExecutionContext?.() ?? {};
46
- const toolDef = this.toolRegistry.commandToTool(command, schemaContext);
47
- const commandId = command.id;
48
- const toolName = toolDef.function.name;
49
- const toTextContent = (t) => this.textContent(t);
50
- modelContext.registerTool({
51
- name: toolName,
52
- description: toolDef.function.description,
53
- inputSchema: this.toolDefToInputSchema(toolDef.function.parameters),
54
- async execute(args) {
55
- try {
56
- const execContext = commandRegistry.createExecutionContext?.(args) ?? { params: args ?? {} };
57
- const result = await commandRegistry.execute(commandId, execContext);
58
- return toTextContent(result === void 0 || result === null ? "Done" : typeof result === "object" ? JSON.stringify(result) : String(result));
59
- } catch (err) {
60
- return toTextContent(`Error: ${err instanceof Error ? err.message : String(err)}`);
61
- }
62
- }
63
- });
64
- this.registeredNames.add(toolName);
65
- this.registeredIds.add(command.id);
66
- }
67
- };
68
- //#endregion
69
- //#region src/webmcp-extension.ts
70
- var webmcp_extension_default = async (_uiContext) => {
71
- await new WebMCPService().start();
72
- };
73
- //#endregion
74
- export { webmcp_extension_default as default };
75
-
76
- //# sourceMappingURL=webmcp-extension-vZl42m8K.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"webmcp-extension-vZl42m8K.js","names":[],"sources":["../src/webmcp-service.ts","../src/webmcp-extension.ts"],"sourcesContent":["import {\n commandRegistry,\n subscribe,\n unsubscribe,\n TOPIC_COMMAND_REGISTERED,\n} from \"@eclipse-docks/core\";\nimport { ToolRegistry, type ToolDefinition } from \"@eclipse-docks/extension-ai-system/api\";\nimport type { Command } from \"@eclipse-docks/core\";\n\nexport class WebMCPService {\n private readonly toolRegistry = new ToolRegistry();\n private readonly registeredNames = new Set<string>();\n private readonly registeredIds = new Set<string>();\n private commandSubscriptionToken: ReturnType<typeof subscribe> | null = null;\n\n async start(): Promise<void> {\n const initialCommands = Object.values(commandRegistry.listCommands()) as Command[];\n for (const command of initialCommands) {\n this.registerCommand(command);\n }\n\n this.commandSubscriptionToken = subscribe(TOPIC_COMMAND_REGISTERED, (command: Command) => {\n this.registerCommand(command);\n });\n }\n\n stop(): void {\n if (this.commandSubscriptionToken !== null) {\n unsubscribe(this.commandSubscriptionToken);\n this.commandSubscriptionToken = null;\n }\n const modelContext = navigator.modelContext;\n if (modelContext) {\n this.registeredNames.forEach((name) => modelContext.unregisterTool(name));\n }\n this.registeredNames.clear();\n this.registeredIds.clear();\n }\n\n private toolDefToInputSchema(\n params: ToolDefinition[\"function\"][\"parameters\"]\n ): { type: \"object\"; properties?: Record<string, unknown>; required?: string[] } {\n return {\n type: \"object\",\n properties: params.properties,\n required: params.required,\n };\n }\n\n private textContent(\n text: string\n ): { content: Array<{ type: \"text\"; text: string }> } {\n return { content: [{ type: \"text\", text }] };\n }\n\n private registerCommand(command: Command): void {\n if (this.registeredIds.has(command.id)) return;\n const modelContext = navigator.modelContext;\n if (!modelContext) return;\n\n const schemaContext = commandRegistry.createExecutionContext?.() ?? {};\n const toolDef = this.toolRegistry.commandToTool(command, schemaContext) as ToolDefinition;\n const commandId = command.id;\n const toolName = toolDef.function.name;\n const toTextContent = (t: string) => this.textContent(t);\n\n modelContext.registerTool({\n name: toolName,\n description: toolDef.function.description,\n inputSchema: this.toolDefToInputSchema(toolDef.function.parameters),\n async execute(args: Record<string, unknown>) {\n try {\n const execContext =\n commandRegistry.createExecutionContext?.(args as Record<string, unknown>) ?? {\n params: args ?? {},\n };\n const result = await commandRegistry.execute(commandId, execContext);\n const text =\n result === undefined || result === null\n ? \"Done\"\n : typeof result === \"object\"\n ? JSON.stringify(result)\n : String(result);\n return toTextContent(text);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return toTextContent(`Error: ${message}`);\n }\n },\n });\n this.registeredNames.add(toolName);\n this.registeredIds.add(command.id);\n }\n}\n","import { WebMCPService } from \"./webmcp-service\";\n\nexport default async (_uiContext: unknown): Promise<void> => {\n const service = new WebMCPService();\n await service.start();\n};\n"],"mappings":";;;AASA,IAAa,gBAAb,MAA2B;;sBACO,IAAI,cAAc;yCACf,IAAI,KAAa;uCACnB,IAAI,KAAa;kCACsB;;CAExE,MAAM,QAAuB;EAC3B,MAAM,kBAAkB,OAAO,OAAO,gBAAgB,cAAc,CAAC;AACrE,OAAK,MAAM,WAAW,gBACpB,MAAK,gBAAgB,QAAQ;AAG/B,OAAK,2BAA2B,UAAU,2BAA2B,YAAqB;AACxF,QAAK,gBAAgB,QAAQ;IAC7B;;CAGJ,OAAa;AACX,MAAI,KAAK,6BAA6B,MAAM;AAC1C,eAAY,KAAK,yBAAyB;AAC1C,QAAK,2BAA2B;;EAElC,MAAM,eAAe,UAAU;AAC/B,MAAI,aACF,MAAK,gBAAgB,SAAS,SAAS,aAAa,eAAe,KAAK,CAAC;AAE3E,OAAK,gBAAgB,OAAO;AAC5B,OAAK,cAAc,OAAO;;CAG5B,qBACE,QAC+E;AAC/E,SAAO;GACL,MAAM;GACN,YAAY,OAAO;GACnB,UAAU,OAAO;GAClB;;CAGH,YACE,MACoD;AACpD,SAAO,EAAE,SAAS,CAAC;GAAE,MAAM;GAAQ;GAAM,CAAC,EAAE;;CAG9C,gBAAwB,SAAwB;AAC9C,MAAI,KAAK,cAAc,IAAI,QAAQ,GAAG,CAAE;EACxC,MAAM,eAAe,UAAU;AAC/B,MAAI,CAAC,aAAc;EAEnB,MAAM,gBAAgB,gBAAgB,0BAA0B,IAAI,EAAE;EACtE,MAAM,UAAU,KAAK,aAAa,cAAc,SAAS,cAAc;EACvE,MAAM,YAAY,QAAQ;EAC1B,MAAM,WAAW,QAAQ,SAAS;EAClC,MAAM,iBAAiB,MAAc,KAAK,YAAY,EAAE;AAExD,eAAa,aAAa;GACxB,MAAM;GACN,aAAa,QAAQ,SAAS;GAC9B,aAAa,KAAK,qBAAqB,QAAQ,SAAS,WAAW;GACnE,MAAM,QAAQ,MAA+B;AAC3C,QAAI;KACF,MAAM,cACJ,gBAAgB,yBAAyB,KAAgC,IAAI,EAC3E,QAAQ,QAAQ,EAAE,EACnB;KACH,MAAM,SAAS,MAAM,gBAAgB,QAAQ,WAAW,YAAY;AAOpE,YAAO,cALL,WAAW,KAAA,KAAa,WAAW,OAC/B,SACA,OAAO,WAAW,WAChB,KAAK,UAAU,OAAO,GACtB,OAAO,OAAO,CACI;aACnB,KAAK;AAEZ,YAAO,cAAc,UADL,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GACvB;;;GAG9C,CAAC;AACF,OAAK,gBAAgB,IAAI,SAAS;AAClC,OAAK,cAAc,IAAI,QAAQ,GAAG;;;;;ACzFtC,IAAA,2BAAe,OAAO,eAAuC;AAE3D,OADgB,IAAI,eAAe,CACrB,OAAO"}