@mcp-fe/event-tracker 0.0.17 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +490 -180
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -1,21 +1,21 @@
1
- const m = typeof process < "u" && process.env?.NODE_ENV === "production", f = typeof process < "u" && process.env?.MCP_DEBUG, p = f === "true" || !m && f !== "false", n = {
2
- log: (...t) => {
3
- p && console.log(...t);
1
+ const T = typeof process < "u" && process.env?.NODE_ENV === "production", p = typeof process < "u" && process.env?.MCP_DEBUG, v = p === "true" || !T && p !== "false", i = {
2
+ log: (...s) => {
3
+ v && console.log(...s);
4
4
  },
5
- debug: (...t) => {
6
- p && console.debug(...t);
5
+ debug: (...s) => {
6
+ v && console.debug(...s);
7
7
  },
8
- info: (...t) => {
9
- console.info(...t);
8
+ info: (...s) => {
9
+ console.info(...s);
10
10
  },
11
- error: (...t) => {
12
- console.error(...t);
11
+ error: (...s) => {
12
+ console.error(...s);
13
13
  },
14
- warn: (...t) => {
15
- p && console.warn(...t);
14
+ warn: (...s) => {
15
+ v && console.warn(...s);
16
16
  }
17
17
  };
18
- class w {
18
+ class C {
19
19
  // Configurable worker script URLs (defaults kept for backward compatibility)
20
20
  sharedWorkerUrl = "/mcp-shared-worker.js";
21
21
  serviceWorkerUrl = "/mcp-service-worker.js";
@@ -30,68 +30,127 @@ class w {
30
30
  connectionStatusCallbacks = /* @__PURE__ */ new Set();
31
31
  // Mutex/promise to prevent concurrent init runs
32
32
  initPromise = null;
33
+ // Initialization state
34
+ isInitialized = !1;
35
+ initResolvers = [];
36
+ // Queue for operations that need to wait for initialization
37
+ pendingRegistrations = [];
38
+ // Map to store tool handlers in main thread
39
+ toolHandlers = /* @__PURE__ */ new Map();
40
+ // Tool registry for tracking registrations and reference counting
41
+ toolRegistry = /* @__PURE__ */ new Map();
42
+ // Subscribers for tool changes (for React hooks reactivity)
43
+ toolChangeListeners = /* @__PURE__ */ new Map();
33
44
  // Initialize and choose worker implementation (prefer SharedWorker)
34
45
  // Accept either a ServiceWorkerRegistration OR WorkerInitOptions to configure URLs
35
46
  async init(e) {
36
- n.log("[WorkerClient] init() called", {
47
+ i.log("[WorkerClient] init() called", {
37
48
  hasOptions: !!e,
38
49
  currentWorkerType: this.workerType,
39
50
  initInProgress: !!this.initPromise,
40
51
  timestamp: Date.now()
41
52
  });
42
53
  let r;
43
- const o = e;
44
- if (o && typeof o.scope == "string")
45
- r = e, n.log("[WorkerClient] Using explicit ServiceWorker registration");
54
+ const t = e;
55
+ if (t && typeof t.scope == "string")
56
+ r = e, i.log("[WorkerClient] Using explicit ServiceWorker registration");
46
57
  else if (e) {
47
- const i = e;
48
- n.log("[WorkerClient] Using WorkerClientInitOptions:", i), i.sharedWorkerUrl && (this.sharedWorkerUrl = i.sharedWorkerUrl), i.serviceWorkerUrl && (this.serviceWorkerUrl = i.serviceWorkerUrl), i.backendWsUrl && (this.backendWsUrl = i.backendWsUrl);
58
+ const o = e;
59
+ i.log("[WorkerClient] Using WorkerClientInitOptions:", o), o.sharedWorkerUrl && (this.sharedWorkerUrl = o.sharedWorkerUrl), o.serviceWorkerUrl && (this.serviceWorkerUrl = o.serviceWorkerUrl), o.backendWsUrl && (this.backendWsUrl = o.backendWsUrl);
49
60
  }
50
61
  return this.initPromise ? this.initPromise.then(async () => {
51
62
  r && this.workerType !== "service" && await this.init(r);
52
63
  }) : (this.initPromise = (async () => {
53
64
  try {
54
65
  if (r) {
55
- this.serviceWorkerRegistration = r, this.workerType = "service", n.info(
66
+ this.serviceWorkerRegistration = r, this.workerType = "service", i.info(
56
67
  "[WorkerClient] Using ServiceWorker (explicit registration)"
57
68
  );
58
69
  try {
59
- const s = {
70
+ const n = {
60
71
  type: "INIT",
61
72
  backendUrl: this.backendWsUrl
62
73
  };
63
- this.pendingAuthToken && (s.token = this.pendingAuthToken), this.serviceWorkerRegistration.active ? this.serviceWorkerRegistration.active.postMessage(s) : "serviceWorker" in navigator && navigator.serviceWorker.controller && navigator.serviceWorker.controller.postMessage(s);
74
+ this.pendingAuthToken && (n.token = this.pendingAuthToken), this.serviceWorkerRegistration.active ? this.serviceWorkerRegistration.active.postMessage(n) : "serviceWorker" in navigator && navigator.serviceWorker.controller && navigator.serviceWorker.controller.postMessage(n);
64
75
  } catch {
65
76
  }
66
77
  return;
67
78
  }
68
- if (await this.initSharedWorker()) return;
69
- await this.initServiceWorkerFallback();
79
+ if (await this.initSharedWorker()) {
80
+ this.markAsInitialized();
81
+ return;
82
+ }
83
+ await this.initServiceWorkerFallback(), this.markAsInitialized();
70
84
  } finally {
71
85
  this.initPromise = null;
72
86
  }
73
87
  })(), this.initPromise);
74
88
  }
89
+ /**
90
+ * Mark worker as initialized and process pending registrations
91
+ * @private
92
+ */
93
+ markAsInitialized() {
94
+ this.isInitialized = !0, i.log(
95
+ "[WorkerClient] Worker initialized, processing pending operations"
96
+ ), this.initResolvers.forEach((r) => r()), this.initResolvers = [];
97
+ const e = [...this.pendingRegistrations];
98
+ this.pendingRegistrations = [], e.forEach(
99
+ async ({ name: r, description: t, inputSchema: o, handler: n, resolve: c, reject: h }) => {
100
+ try {
101
+ await this.registerToolInternal(
102
+ r,
103
+ t,
104
+ o,
105
+ n
106
+ ), c();
107
+ } catch (d) {
108
+ h(d instanceof Error ? d : new Error(String(d)));
109
+ }
110
+ }
111
+ );
112
+ }
113
+ /**
114
+ * Wait for worker initialization
115
+ * @returns Promise that resolves when worker is initialized
116
+ */
117
+ async waitForInit() {
118
+ if (this.isInitialized)
119
+ return Promise.resolve();
120
+ if (this.initPromise) {
121
+ await this.initPromise;
122
+ return;
123
+ }
124
+ return new Promise((e) => {
125
+ this.initResolvers.push(e);
126
+ });
127
+ }
128
+ /**
129
+ * Check if worker is initialized
130
+ */
131
+ get initialized() {
132
+ return this.isInitialized;
133
+ }
75
134
  async initSharedWorker() {
76
135
  if (typeof SharedWorker > "u")
77
- return !1;
136
+ return Promise.resolve(!1);
78
137
  try {
79
138
  this.sharedWorker = new SharedWorker(this.sharedWorkerUrl, {
80
139
  type: "module"
81
- }), this.sharedWorkerPort = this.sharedWorker.port, this.sharedWorkerPort.start(), await new Promise((r, o) => {
82
- let i = !1;
83
- const s = setTimeout(() => {
84
- if (!i) {
85
- const k = this.sharedWorkerPort;
86
- k && (k.onmessage = null), o(new Error("SharedWorker initialization timeout"));
140
+ }), this.sharedWorkerPort = this.sharedWorker.port, this.sharedWorkerPort.start(), await new Promise((r, t) => {
141
+ let o = !1;
142
+ const n = setTimeout(() => {
143
+ if (!o) {
144
+ const h = this.sharedWorkerPort;
145
+ h && (h.onmessage = null), t(new Error("SharedWorker initialization timeout"));
87
146
  }
88
- }, 2e3), l = this.sharedWorkerPort;
89
- if (!l)
90
- return clearTimeout(s), o(new Error("SharedWorker port not available"));
91
- l.onmessage = (k) => {
147
+ }, 2e3), c = this.sharedWorkerPort;
148
+ if (!c)
149
+ return clearTimeout(n), t(new Error("SharedWorker port not available"));
150
+ c.onmessage = (h) => {
92
151
  try {
93
- const h = k.data;
94
- h && h.type === "CONNECTION_STATUS" && (clearTimeout(s), i = !0, this.workerType = "shared", l.onmessage = null, r());
152
+ const d = h.data;
153
+ d && d.type === "CONNECTION_STATUS" && (clearTimeout(n), o = !0, this.workerType = "shared", c.onmessage = null, r(!0));
95
154
  } catch {
96
155
  }
97
156
  };
@@ -105,30 +164,37 @@ class w {
105
164
  };
106
165
  this.pendingAuthToken && (r.token = this.pendingAuthToken), e.postMessage(r), this.pendingAuthToken = null;
107
166
  } catch (r) {
108
- n.warn(
167
+ i.warn(
109
168
  "[WorkerClient] Failed to send INIT to SharedWorker port:",
110
169
  r
111
170
  );
112
171
  }
113
172
  e.onmessage = (r) => {
114
173
  try {
115
- const o = r.data;
116
- if (o && o.type === "CONNECTION_STATUS") {
117
- const i = !!o.connected;
118
- this.connectionStatusCallbacks.forEach((s) => {
174
+ const t = r.data;
175
+ if (t && t.type === "CONNECTION_STATUS") {
176
+ const o = !!t.connected;
177
+ this.connectionStatusCallbacks.forEach((n) => {
119
178
  try {
120
- s(i);
179
+ n(o);
121
180
  } catch {
122
181
  }
123
182
  });
124
- }
183
+ } else t && t.type === "CALL_TOOL" && this.handleToolCall(t.toolName, t.args, t.callId).catch(
184
+ (o) => {
185
+ i.error(
186
+ "[WorkerClient] Failed to handle tool call:",
187
+ o
188
+ );
189
+ }
190
+ );
125
191
  } catch {
126
192
  }
127
193
  };
128
194
  }
129
- return n.info("[WorkerClient] Using SharedWorker"), !0;
195
+ return i.info("[WorkerClient] Using SharedWorker"), !0;
130
196
  } catch (e) {
131
- return n.warn(
197
+ return i.warn(
132
198
  "[WorkerClient] SharedWorker not available, falling back to ServiceWorker:",
133
199
  e
134
200
  ), !1;
@@ -139,14 +205,36 @@ class w {
139
205
  try {
140
206
  const e = await navigator.serviceWorker.getRegistration();
141
207
  if (e) {
142
- this.serviceWorkerRegistration = e, this.workerType = "service", n.info(
208
+ this.serviceWorkerRegistration = e, this.workerType = "service", i.info(
143
209
  "[WorkerClient] Using existing ServiceWorker registration"
144
210
  );
145
211
  return;
146
212
  }
147
213
  this.serviceWorkerRegistration = await navigator.serviceWorker.register(
148
214
  this.serviceWorkerUrl
149
- ), this.workerType = "service", n.info("[WorkerClient] Using MCP ServiceWorker (fallback)");
215
+ ), this.workerType = "service", "serviceWorker" in navigator && navigator.serviceWorker.addEventListener(
216
+ "message",
217
+ (r) => {
218
+ try {
219
+ const t = r.data;
220
+ t && t.type === "CALL_TOOL" && this.handleToolCall(
221
+ t.toolName,
222
+ t.args,
223
+ t.callId
224
+ ).catch((o) => {
225
+ i.error(
226
+ "[WorkerClient] Failed to handle tool call:",
227
+ o
228
+ );
229
+ });
230
+ } catch (t) {
231
+ i.error(
232
+ "[WorkerClient] Error processing ServiceWorker message:",
233
+ t
234
+ );
235
+ }
236
+ }
237
+ ), i.info("[WorkerClient] Using MCP ServiceWorker (fallback)");
150
238
  try {
151
239
  const r = {
152
240
  type: "INIT",
@@ -156,105 +244,105 @@ class w {
156
244
  } catch {
157
245
  }
158
246
  } catch (e) {
159
- throw n.error("[WorkerClient] Failed to register ServiceWorker:", e), e;
247
+ throw i.error("[WorkerClient] Failed to register ServiceWorker:", e), e;
160
248
  }
161
249
  else
162
250
  throw new Error("Neither SharedWorker nor ServiceWorker is supported");
163
251
  }
164
252
  // Low-level request that expects a reply via MessageChannel
165
- async request(e, r, o = 5e3) {
166
- if (n.log("[WorkerClient] Request started:", {
253
+ async request(e, r, t = 5e3) {
254
+ if (i.log("[WorkerClient] Request started:", {
167
255
  type: e,
168
256
  payload: r,
169
- timeoutMs: o,
257
+ timeoutMs: t,
170
258
  workerType: this.workerType,
171
259
  hasSharedWorkerPort: !!this.sharedWorkerPort,
172
260
  hasServiceWorkerReg: !!this.serviceWorkerRegistration
173
261
  }), this.workerType === "shared" && this.sharedWorkerPort)
174
- return new Promise((i, s) => {
175
- const l = new MessageChannel(), k = Math.random().toString(36).substring(7), h = Date.now(), d = setTimeout(() => {
176
- const a = Date.now() - h;
177
- n.error("[WorkerClient] Request timeout:", {
262
+ return new Promise((o, n) => {
263
+ const c = new MessageChannel(), h = Math.random().toString(36).substring(7), d = Date.now(), g = setTimeout(() => {
264
+ const a = Date.now() - d;
265
+ i.error("[WorkerClient] Request timeout:", {
178
266
  type: e,
179
- requestId: k,
267
+ requestId: h,
180
268
  elapsed: a,
181
- timeoutMs: o
182
- }), l.port1.onmessage = null, s(new Error("Request timeout"));
183
- }, o);
184
- l.port1.onmessage = (a) => {
269
+ timeoutMs: t
270
+ }), c.port1.onmessage = null, n(new Error("Request timeout"));
271
+ }, t);
272
+ c.port1.onmessage = (a) => {
185
273
  try {
186
- const c = Date.now() - h;
187
- n.log("[WorkerClient] Request response received:", {
274
+ const l = Date.now() - d;
275
+ i.log("[WorkerClient] Request response received:", {
188
276
  type: e,
189
- requestId: k,
190
- elapsed: c,
277
+ requestId: h,
278
+ elapsed: l,
191
279
  success: a.data?.success
192
- }), clearTimeout(d), a.data && a.data.success ? i(a.data) : a.data && a.data.success === !1 ? s(new Error(a.data.error || "Worker error")) : i(a.data);
193
- } catch (c) {
194
- clearTimeout(d), l.port1.onmessage = null, s(
195
- c instanceof Error ? c : new Error(String(c))
280
+ }), clearTimeout(g), a.data && a.data.success ? o(a.data) : a.data && a.data.success === !1 ? n(new Error(a.data.error || "Worker error")) : o(a.data);
281
+ } catch (l) {
282
+ clearTimeout(g), c.port1.onmessage = null, n(
283
+ l instanceof Error ? l : new Error(String(l))
196
284
  );
197
285
  }
198
286
  };
199
287
  try {
200
288
  const a = this.sharedWorkerPort;
201
289
  if (!a)
202
- return clearTimeout(d), n.error("[WorkerClient] SharedWorker port not available"), s(new Error("SharedWorker port not available"));
203
- n.log("[WorkerClient] Posting message to SharedWorker:", {
290
+ return clearTimeout(g), i.error("[WorkerClient] SharedWorker port not available"), n(new Error("SharedWorker port not available"));
291
+ i.log("[WorkerClient] Posting message to SharedWorker:", {
204
292
  type: e,
205
- requestId: k
206
- }), a.postMessage({ type: e, ...r || {} }, [l.port2]);
293
+ requestId: h
294
+ }), a.postMessage({ type: e, ...r || {} }, [c.port2]);
207
295
  } catch (a) {
208
- clearTimeout(d), n.error("[WorkerClient] Failed to post message:", a), s(a instanceof Error ? a : new Error(String(a)));
296
+ clearTimeout(g), i.error("[WorkerClient] Failed to post message:", a), n(a instanceof Error ? a : new Error(String(a)));
209
297
  }
210
298
  });
211
299
  if (this.workerType === "service" && this.serviceWorkerRegistration) {
212
- const i = this.serviceWorkerRegistration;
213
- if (!i) throw new Error("Service worker registration missing");
214
- if (!i.active && (n.log("[WorkerClient] ServiceWorker not active, waiting..."), await navigator.serviceWorker.ready, !i.active))
300
+ const o = this.serviceWorkerRegistration;
301
+ if (!o) throw new Error("Service worker registration missing");
302
+ if (!o.active && (i.log("[WorkerClient] ServiceWorker not active, waiting..."), await navigator.serviceWorker.ready, !o.active))
215
303
  throw new Error("Service worker not active");
216
- return new Promise((s, l) => {
217
- const k = new MessageChannel(), h = Math.random().toString(36).substring(7), d = Date.now(), a = setTimeout(() => {
218
- const c = Date.now() - d;
219
- n.error("[WorkerClient] ServiceWorker request timeout:", {
304
+ return new Promise((n, c) => {
305
+ const h = new MessageChannel(), d = Math.random().toString(36).substring(7), g = Date.now(), a = setTimeout(() => {
306
+ const l = Date.now() - g;
307
+ i.error("[WorkerClient] ServiceWorker request timeout:", {
220
308
  type: e,
221
- requestId: h,
222
- elapsed: c,
223
- timeoutMs: o
224
- }), k.port1.onmessage = null, l(new Error("Request timeout"));
225
- }, o);
226
- k.port1.onmessage = (c) => {
309
+ requestId: d,
310
+ elapsed: l,
311
+ timeoutMs: t
312
+ }), h.port1.onmessage = null, c(new Error("Request timeout"));
313
+ }, t);
314
+ h.port1.onmessage = (l) => {
227
315
  try {
228
- const g = Date.now() - d;
229
- n.log("[WorkerClient] ServiceWorker response received:", {
316
+ const u = Date.now() - g;
317
+ i.log("[WorkerClient] ServiceWorker response received:", {
230
318
  type: e,
231
- requestId: h,
232
- elapsed: g,
233
- success: c.data?.success
234
- }), clearTimeout(a), c.data && c.data.success ? s(c.data) : c.data && c.data.success === !1 ? l(new Error(c.data.error || "Worker error")) : s(c.data);
235
- } catch (g) {
236
- clearTimeout(a), k.port1.onmessage = null, l(
237
- g instanceof Error ? g : new Error(String(g))
319
+ requestId: d,
320
+ elapsed: u,
321
+ success: l.data?.success
322
+ }), clearTimeout(a), l.data && l.data.success ? n(l.data) : l.data && l.data.success === !1 ? c(new Error(l.data.error || "Worker error")) : n(l.data);
323
+ } catch (u) {
324
+ clearTimeout(a), h.port1.onmessage = null, c(
325
+ u instanceof Error ? u : new Error(String(u))
238
326
  );
239
327
  }
240
328
  };
241
329
  try {
242
- const c = i.active;
243
- if (!c)
244
- return clearTimeout(a), n.error(
330
+ const l = o.active;
331
+ if (!l)
332
+ return clearTimeout(a), i.error(
245
333
  "[WorkerClient] ServiceWorker active instance not available"
246
- ), l(
334
+ ), c(
247
335
  new Error("Service worker active instance not available")
248
336
  );
249
- n.log("[WorkerClient] Posting message to ServiceWorker:", {
337
+ i.log("[WorkerClient] Posting message to ServiceWorker:", {
250
338
  type: e,
251
- requestId: h
252
- }), c.postMessage({ type: e, ...r || {} }, [k.port2]);
253
- } catch (c) {
254
- clearTimeout(a), n.error(
339
+ requestId: d
340
+ }), l.postMessage({ type: e, ...r || {} }, [h.port2]);
341
+ } catch (l) {
342
+ clearTimeout(a), i.error(
255
343
  "[WorkerClient] Failed to post message to ServiceWorker:",
256
- c
257
- ), l(c instanceof Error ? c : new Error(String(c)));
344
+ l
345
+ ), c(l instanceof Error ? l : new Error(String(l)));
258
346
  }
259
347
  });
260
348
  }
@@ -265,8 +353,8 @@ class w {
265
353
  if (this.workerType === "shared" && this.sharedWorkerPort) {
266
354
  try {
267
355
  this.sharedWorkerPort.postMessage({ type: e, ...r || {} });
268
- } catch (o) {
269
- n.error("[WorkerClient] Failed to post to SharedWorker:", o);
356
+ } catch (t) {
357
+ i.error("[WorkerClient] Failed to post to SharedWorker:", t);
270
358
  }
271
359
  return;
272
360
  }
@@ -276,10 +364,10 @@ class w {
276
364
  type: e,
277
365
  ...r || {}
278
366
  });
279
- } catch (o) {
280
- n.error(
367
+ } catch (t) {
368
+ i.error(
281
369
  "[WorkerClient] Failed to post to ServiceWorker (active):",
282
- o
370
+ t
283
371
  );
284
372
  }
285
373
  return;
@@ -290,17 +378,17 @@ class w {
290
378
  type: e,
291
379
  ...r || {}
292
380
  });
293
- } catch (o) {
294
- n.error(
381
+ } catch (t) {
382
+ i.error(
295
383
  "[WorkerClient] Failed to post to ServiceWorker.controller:",
296
- o
384
+ t
297
385
  );
298
386
  }
299
387
  return;
300
388
  }
301
389
  if (e === "SET_AUTH_TOKEN" && r) {
302
- const o = r.token;
303
- typeof o == "string" && (this.pendingAuthToken = o);
390
+ const t = r.token;
391
+ typeof t == "string" && (this.pendingAuthToken = t);
304
392
  }
305
393
  }
306
394
  sendAuthTokenToServiceWorker(e) {
@@ -311,7 +399,7 @@ class w {
311
399
  token: e
312
400
  });
313
401
  } catch (r) {
314
- n.error(
402
+ console.error(
315
403
  "[WorkerClient] Failed to send auth token to ServiceWorker:",
316
404
  r
317
405
  );
@@ -323,7 +411,7 @@ class w {
323
411
  token: e
324
412
  });
325
413
  } catch (r) {
326
- n.error(
414
+ console.error(
327
415
  "[WorkerClient] Failed to send auth token to ServiceWorker.controller:",
328
416
  r
329
417
  );
@@ -355,83 +443,305 @@ class w {
355
443
  try {
356
444
  this.sharedWorkerPort.postMessage({ type: "SET_AUTH_TOKEN", token: e }), this.pendingAuthToken = null;
357
445
  } catch (r) {
358
- n.error(
446
+ i.error(
359
447
  "[WorkerClient] Failed to set auth token on SharedWorker:",
360
448
  r
361
449
  );
362
450
  }
363
451
  else this.workerType === "service" && (this.sendAuthTokenToServiceWorker(e), this.pendingAuthToken = null);
364
452
  }
453
+ /**
454
+ * Register a custom MCP tool dynamically
455
+ *
456
+ * The handler function runs in the MAIN THREAD (browser context), not in the worker.
457
+ * This means you have full access to:
458
+ * - React context, hooks, Redux store
459
+ * - DOM API, window, localStorage
460
+ * - All your imports and dependencies
461
+ * - Closures and external variables
462
+ *
463
+ * The worker acts as a proxy - it receives MCP tool calls and forwards them
464
+ * to your handler via MessageChannel.
465
+ *
466
+ * @param name - Tool name
467
+ * @param description - Tool description
468
+ * @param inputSchema - JSON Schema for tool inputs
469
+ * @param handler - Async function that handles the tool execution (runs in main thread)
470
+ * @returns Promise that resolves when tool is registered
471
+ *
472
+ * @example With full access to imports and context:
473
+ * ```typescript
474
+ * import { z } from 'zod';
475
+ * import { useMyStore } from './store';
476
+ *
477
+ * const store = useMyStore();
478
+ *
479
+ * await client.registerTool(
480
+ * 'validate_user',
481
+ * 'Validate user data',
482
+ * { type: 'object', properties: { username: { type: 'string' } } },
483
+ * async (args: any) => {
484
+ * // Full access to everything!
485
+ * const schema = z.object({ username: z.string().min(3) });
486
+ * const validated = schema.parse(args);
487
+ *
488
+ * // Can access store, context, etc.
489
+ * const currentUser = store.getState().user;
490
+ *
491
+ * return {
492
+ * content: [{
493
+ * type: 'text',
494
+ * text: JSON.stringify({ validated, currentUser })
495
+ * }]
496
+ * };
497
+ * }
498
+ * );
499
+ * ```
500
+ */
501
+ async registerTool(e, r, t, o) {
502
+ return this.isInitialized ? this.registerToolInternal(e, r, t, o) : (i.log(
503
+ `[WorkerClient] Queueing tool registration '${e}' (worker not initialized yet)`
504
+ ), new Promise((n, c) => {
505
+ this.pendingRegistrations.push({
506
+ name: e,
507
+ description: r,
508
+ inputSchema: t,
509
+ handler: o,
510
+ resolve: n,
511
+ reject: c
512
+ });
513
+ }));
514
+ }
515
+ /**
516
+ * Internal method to register tool (assumes worker is initialized)
517
+ * @private
518
+ */
519
+ async registerToolInternal(e, r, t, o) {
520
+ const n = this.toolRegistry.get(e);
521
+ if (n) {
522
+ n.refCount++, i.log(
523
+ `[WorkerClient] Incremented ref count for '${e}': ${n.refCount}`
524
+ ), this.toolHandlers.set(e, o), this.notifyToolChange(e);
525
+ return;
526
+ }
527
+ this.toolHandlers.set(e, o), await this.request("REGISTER_TOOL", {
528
+ name: e,
529
+ description: r,
530
+ inputSchema: t,
531
+ handlerType: "proxy"
532
+ // Tell worker this is a proxy handler
533
+ }), this.toolRegistry.set(e, {
534
+ refCount: 1,
535
+ description: r,
536
+ inputSchema: t,
537
+ isRegistered: !0
538
+ }), i.log(
539
+ `[WorkerClient] Registered tool '${e}' with main-thread handler`
540
+ ), this.notifyToolChange(e);
541
+ }
542
+ /**
543
+ * Unregister a previously registered MCP tool
544
+ * @param name - Tool name to unregister
545
+ * @returns Promise that resolves to true if tool was found and removed
546
+ */
547
+ async unregisterTool(e) {
548
+ const r = this.toolRegistry.get(e);
549
+ if (!r)
550
+ return i.warn(`[WorkerClient] Cannot unregister '${e}': not found`), !1;
551
+ if (r.refCount--, i.log(
552
+ `[WorkerClient] Decremented ref count for '${e}': ${r.refCount}`
553
+ ), r.refCount <= 0) {
554
+ this.toolHandlers.delete(e);
555
+ const t = await this.request(
556
+ "UNREGISTER_TOOL",
557
+ { name: e }
558
+ );
559
+ return this.toolRegistry.delete(e), i.log(`[WorkerClient] Unregistered tool '${e}'`), this.notifyToolChange(e), t?.success ?? !1;
560
+ }
561
+ return this.notifyToolChange(e), !0;
562
+ }
563
+ /**
564
+ * Subscribe to tool changes for a specific tool
565
+ * Returns unsubscribe function
566
+ */
567
+ onToolChange(e, r) {
568
+ this.toolChangeListeners.has(e) || this.toolChangeListeners.set(e, /* @__PURE__ */ new Set());
569
+ const t = this.toolChangeListeners.get(e);
570
+ t.add(r);
571
+ const o = this.toolRegistry.get(e);
572
+ return r(o ? {
573
+ refCount: o.refCount,
574
+ isRegistered: o.isRegistered
575
+ } : null), () => {
576
+ t.delete(r), t.size === 0 && this.toolChangeListeners.delete(e);
577
+ };
578
+ }
579
+ /**
580
+ * Notify all listeners about tool changes
581
+ * @private
582
+ */
583
+ notifyToolChange(e) {
584
+ const r = this.toolChangeListeners.get(e);
585
+ if (!r || r.size === 0) return;
586
+ const t = this.toolRegistry.get(e), o = t ? {
587
+ refCount: t.refCount,
588
+ isRegistered: t.isRegistered
589
+ } : null;
590
+ r.forEach((n) => {
591
+ try {
592
+ n(o);
593
+ } catch (c) {
594
+ i.error("[WorkerClient] Error in tool change listener:", c);
595
+ }
596
+ });
597
+ }
598
+ /**
599
+ * Get tool info from registry
600
+ */
601
+ getToolInfo(e) {
602
+ const r = this.toolRegistry.get(e);
603
+ return r ? {
604
+ refCount: r.refCount,
605
+ isRegistered: r.isRegistered
606
+ } : null;
607
+ }
608
+ /**
609
+ * Get all registered tool names
610
+ */
611
+ getRegisteredTools() {
612
+ return Array.from(this.toolRegistry.keys()).filter(
613
+ (e) => this.toolRegistry.get(e)?.isRegistered
614
+ );
615
+ }
616
+ /**
617
+ * Check if a tool is registered
618
+ */
619
+ isToolRegistered(e) {
620
+ return this.toolRegistry.get(e)?.isRegistered ?? !1;
621
+ }
622
+ /**
623
+ * Handle tool call from worker - execute handler in main thread and return result
624
+ * @private
625
+ */
626
+ async handleToolCall(e, r, t) {
627
+ i.log(`[WorkerClient] Handling tool call: ${e}`, {
628
+ callId: t,
629
+ args: r
630
+ });
631
+ try {
632
+ const o = this.toolHandlers.get(e);
633
+ if (!o)
634
+ throw new Error(`Tool handler not found: ${e}`);
635
+ const n = await o(r);
636
+ this.sendToolCallResult(t, { success: !0, result: n });
637
+ } catch (o) {
638
+ i.error(`[WorkerClient] Tool call failed: ${e}`, o), this.sendToolCallResult(t, {
639
+ success: !1,
640
+ error: o instanceof Error ? o.message : "Unknown error"
641
+ });
642
+ }
643
+ }
644
+ /**
645
+ * Send tool call result back to worker
646
+ * @private
647
+ */
648
+ sendToolCallResult(e, r) {
649
+ const t = {
650
+ type: "TOOL_CALL_RESULT",
651
+ callId: e,
652
+ success: r.success,
653
+ result: r.result,
654
+ error: r.error
655
+ };
656
+ if (this.workerType === "shared" && this.sharedWorkerPort)
657
+ try {
658
+ this.sharedWorkerPort.postMessage(t);
659
+ } catch (o) {
660
+ i.error(
661
+ "[WorkerClient] Failed to send result to SharedWorker:",
662
+ o
663
+ );
664
+ }
665
+ else if (this.workerType === "service" && this.serviceWorkerRegistration?.active)
666
+ try {
667
+ this.serviceWorkerRegistration.active.postMessage(t);
668
+ } catch (o) {
669
+ i.error(
670
+ "[WorkerClient] Failed to send result to ServiceWorker:",
671
+ o
672
+ );
673
+ }
674
+ }
365
675
  }
366
- const T = "user-activity-db", S = 1, W = "user-events";
367
- async function y() {
368
- return new Promise((t, e) => {
369
- const r = indexedDB.open(T, S);
370
- r.onerror = () => e(r.error), r.onsuccess = () => t(r.result), r.onupgradeneeded = (o) => {
371
- const i = o.target.result;
372
- if (!i.objectStoreNames.contains(W)) {
373
- const s = i.createObjectStore(W, { keyPath: "id" });
374
- s.createIndex("timestamp", "timestamp", { unique: !1 }), s.createIndex("type", "type", { unique: !1 }), s.createIndex("path", "path", { unique: !1 });
676
+ const w = "user-activity-db", y = 1, f = "user-events";
677
+ async function S() {
678
+ return new Promise((s, e) => {
679
+ const r = indexedDB.open(w, y);
680
+ r.onerror = () => e(r.error), r.onsuccess = () => s(r.result), r.onupgradeneeded = (t) => {
681
+ const o = t.target.result;
682
+ if (!o.objectStoreNames.contains(f)) {
683
+ const n = o.createObjectStore(f, { keyPath: "id" });
684
+ n.createIndex("timestamp", "timestamp", { unique: !1 }), n.createIndex("type", "type", { unique: !1 }), n.createIndex("path", "path", { unique: !1 });
375
685
  }
376
686
  };
377
687
  });
378
688
  }
379
- async function C(t) {
380
- const i = (await y()).transaction([W], "readonly").objectStore(W).index("timestamp");
381
- return new Promise((s, l) => {
382
- const k = i.getAll();
383
- k.onsuccess = () => {
384
- let h = k.result;
385
- h.sort((d, a) => a.timestamp - d.timestamp), s(h);
386
- }, k.onerror = () => l(k.error);
689
+ async function m(s) {
690
+ const o = (await S()).transaction([f], "readonly").objectStore(f).index("timestamp");
691
+ return new Promise((n, c) => {
692
+ const h = o.getAll();
693
+ h.onsuccess = () => {
694
+ let d = h.result;
695
+ d.sort((g, a) => a.timestamp - g.timestamp), n(d);
696
+ }, h.onerror = () => c(h.error);
387
697
  });
388
698
  }
389
- const u = new w();
390
- async function E(t) {
391
- return u.init(t);
699
+ const k = new C();
700
+ async function R(s) {
701
+ return k.init(s);
392
702
  }
393
- async function v(t) {
394
- const e = { ...t, timestamp: Date.now() };
395
- await u.request("STORE_EVENT", { event: e });
703
+ async function W(s) {
704
+ const e = { ...s, timestamp: Date.now() };
705
+ await k.request("STORE_EVENT", { event: e });
396
706
  }
397
- async function b() {
398
- const t = await C();
399
- return Array.isArray(t) ? t : [];
707
+ async function E() {
708
+ const s = await m();
709
+ return Array.isArray(s) ? s : [];
400
710
  }
401
- async function U() {
402
- return u.getConnectionStatus();
711
+ async function P() {
712
+ return k.getConnectionStatus();
403
713
  }
404
- function N(t) {
405
- u.setAuthToken(t);
714
+ function A(s) {
715
+ k.setAuthToken(s);
406
716
  }
407
- function P(t) {
408
- u.onConnectionStatus(t);
717
+ function I(s) {
718
+ k.onConnectionStatus(s);
409
719
  }
410
- function A(t) {
411
- u.offConnectionStatus(t);
720
+ function b(s) {
721
+ k.offConnectionStatus(s);
412
722
  }
413
- async function R(t, e, r) {
414
- return v({ type: "navigation", from: t, to: e, path: r || e });
723
+ async function U(s, e, r) {
724
+ return W({ type: "navigation", from: s, to: e, path: r || e });
415
725
  }
416
- async function I(t, e, r) {
417
- const o = t.id || void 0, i = t.className || void 0, s = t.textContent?.trim().substring(0, 100) || void 0, l = t.tagName.toLowerCase();
418
- return v({
726
+ async function N(s, e, r) {
727
+ const t = s.id || void 0, o = s.className || void 0, n = s.textContent?.trim().substring(0, 100) || void 0, c = s.tagName.toLowerCase();
728
+ return W({
419
729
  type: "click",
420
- element: l,
421
- elementId: o,
422
- elementClass: i,
423
- elementText: s,
730
+ element: c,
731
+ elementId: t,
732
+ elementClass: o,
733
+ elementText: n,
424
734
  path: e || window.location.pathname,
425
735
  metadata: r
426
736
  });
427
737
  }
428
- async function M(t, e, r) {
429
- const o = t.id || void 0, i = t.className || void 0, s = t.tagName.toLowerCase();
430
- return v({
738
+ async function M(s, e, r) {
739
+ const t = s.id || void 0, o = s.className || void 0, n = s.tagName.toLowerCase();
740
+ return W({
431
741
  type: "input",
432
- element: s,
433
- elementId: o,
434
- elementClass: i,
742
+ element: n,
743
+ elementId: t,
744
+ elementClass: o,
435
745
  path: r || window.location.pathname,
436
746
  metadata: {
437
747
  valueLength: e?.length || 0,
@@ -439,26 +749,26 @@ async function M(t, e, r) {
439
749
  }
440
750
  });
441
751
  }
442
- async function _(t, e, r) {
443
- return v({
752
+ async function _(s, e, r) {
753
+ return W({
444
754
  type: "custom",
445
755
  path: r || window.location.pathname,
446
756
  metadata: {
447
- eventName: t,
757
+ eventName: s,
448
758
  ...e
449
759
  }
450
760
  });
451
761
  }
452
762
  export {
453
- U as getConnectionStatus,
454
- b as getStoredEvents,
455
- E as initEventTracker,
456
- A as offConnectionStatus,
457
- P as onConnectionStatus,
458
- N as setAuthToken,
459
- I as trackClick,
763
+ P as getConnectionStatus,
764
+ E as getStoredEvents,
765
+ R as initEventTracker,
766
+ b as offConnectionStatus,
767
+ I as onConnectionStatus,
768
+ A as setAuthToken,
769
+ N as trackClick,
460
770
  _ as trackCustom,
461
- v as trackEvent,
771
+ W as trackEvent,
462
772
  M as trackInput,
463
- R as trackNavigation
773
+ U as trackNavigation
464
774
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-fe/event-tracker",
3
- "version": "0.0.17",
3
+ "version": "0.1.0",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://mcp-fe.ai",
6
6
  "repository": {
@@ -13,7 +13,7 @@
13
13
  "main": "./index.js",
14
14
  "types": "./index.d.ts",
15
15
  "dependencies": {
16
- "@mcp-fe/mcp-worker": "0.0.17"
16
+ "@mcp-fe/mcp-worker": "0.1.0"
17
17
  },
18
18
  "publishConfig": {
19
19
  "access": "public"