@mcp-fe/event-tracker 0.0.16 → 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.
- package/LICENSE +1 -1
- package/index.d.ts +1 -1
- package/index.js +517 -121
- package/package.json +2 -2
package/LICENSE
CHANGED
|
@@ -188,7 +188,7 @@
|
|
|
188
188
|
same page as the copyright notice for easier identification within
|
|
189
189
|
third-party archives.
|
|
190
190
|
|
|
191
|
-
Copyright 2026
|
|
191
|
+
Copyright 2026 Michal Kopecky
|
|
192
192
|
|
|
193
193
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
194
194
|
you may not use this file except in compliance with the License.
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
|
-
|
|
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
|
+
},
|
|
5
|
+
debug: (...s) => {
|
|
6
|
+
v && console.debug(...s);
|
|
7
|
+
},
|
|
8
|
+
info: (...s) => {
|
|
9
|
+
console.info(...s);
|
|
10
|
+
},
|
|
11
|
+
error: (...s) => {
|
|
12
|
+
console.error(...s);
|
|
13
|
+
},
|
|
14
|
+
warn: (...s) => {
|
|
15
|
+
v && console.warn(...s);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
class C {
|
|
2
19
|
// Configurable worker script URLs (defaults kept for backward compatibility)
|
|
3
20
|
sharedWorkerUrl = "/mcp-shared-worker.js";
|
|
4
21
|
serviceWorkerUrl = "/mcp-service-worker.js";
|
|
@@ -13,59 +30,127 @@ class g {
|
|
|
13
30
|
connectionStatusCallbacks = /* @__PURE__ */ new Set();
|
|
14
31
|
// Mutex/promise to prevent concurrent init runs
|
|
15
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();
|
|
16
44
|
// Initialize and choose worker implementation (prefer SharedWorker)
|
|
17
45
|
// Accept either a ServiceWorkerRegistration OR WorkerInitOptions to configure URLs
|
|
18
46
|
async init(e) {
|
|
47
|
+
i.log("[WorkerClient] init() called", {
|
|
48
|
+
hasOptions: !!e,
|
|
49
|
+
currentWorkerType: this.workerType,
|
|
50
|
+
initInProgress: !!this.initPromise,
|
|
51
|
+
timestamp: Date.now()
|
|
52
|
+
});
|
|
19
53
|
let r;
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
22
|
-
r = e;
|
|
54
|
+
const t = e;
|
|
55
|
+
if (t && typeof t.scope == "string")
|
|
56
|
+
r = e, i.log("[WorkerClient] Using explicit ServiceWorker registration");
|
|
23
57
|
else if (e) {
|
|
24
58
|
const o = e;
|
|
25
|
-
o.sharedWorkerUrl && (this.sharedWorkerUrl = o.sharedWorkerUrl), o.serviceWorkerUrl && (this.serviceWorkerUrl = o.serviceWorkerUrl), o.backendWsUrl && (this.backendWsUrl = o.backendWsUrl);
|
|
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);
|
|
26
60
|
}
|
|
27
61
|
return this.initPromise ? this.initPromise.then(async () => {
|
|
28
62
|
r && this.workerType !== "service" && await this.init(r);
|
|
29
63
|
}) : (this.initPromise = (async () => {
|
|
30
64
|
try {
|
|
31
65
|
if (r) {
|
|
32
|
-
this.serviceWorkerRegistration = r, this.workerType = "service",
|
|
66
|
+
this.serviceWorkerRegistration = r, this.workerType = "service", i.info(
|
|
33
67
|
"[WorkerClient] Using ServiceWorker (explicit registration)"
|
|
34
68
|
);
|
|
35
69
|
try {
|
|
36
|
-
const n = {
|
|
70
|
+
const n = {
|
|
71
|
+
type: "INIT",
|
|
72
|
+
backendUrl: this.backendWsUrl
|
|
73
|
+
};
|
|
37
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);
|
|
38
75
|
} catch {
|
|
39
76
|
}
|
|
40
77
|
return;
|
|
41
78
|
}
|
|
42
|
-
if (await this.initSharedWorker())
|
|
43
|
-
|
|
79
|
+
if (await this.initSharedWorker()) {
|
|
80
|
+
this.markAsInitialized();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
await this.initServiceWorkerFallback(), this.markAsInitialized();
|
|
44
84
|
} finally {
|
|
45
85
|
this.initPromise = null;
|
|
46
86
|
}
|
|
47
87
|
})(), this.initPromise);
|
|
48
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
|
+
}
|
|
49
134
|
async initSharedWorker() {
|
|
50
135
|
if (typeof SharedWorker > "u")
|
|
51
|
-
return !1;
|
|
136
|
+
return Promise.resolve(!1);
|
|
52
137
|
try {
|
|
53
138
|
this.sharedWorker = new SharedWorker(this.sharedWorkerUrl, {
|
|
54
139
|
type: "module"
|
|
55
|
-
}), this.sharedWorkerPort = this.sharedWorker.port, this.sharedWorkerPort.start(), await new Promise((r,
|
|
140
|
+
}), this.sharedWorkerPort = this.sharedWorker.port, this.sharedWorkerPort.start(), await new Promise((r, t) => {
|
|
56
141
|
let o = !1;
|
|
57
142
|
const n = setTimeout(() => {
|
|
58
143
|
if (!o) {
|
|
59
|
-
const
|
|
60
|
-
|
|
144
|
+
const h = this.sharedWorkerPort;
|
|
145
|
+
h && (h.onmessage = null), t(new Error("SharedWorker initialization timeout"));
|
|
61
146
|
}
|
|
62
147
|
}, 2e3), c = this.sharedWorkerPort;
|
|
63
148
|
if (!c)
|
|
64
|
-
return clearTimeout(n),
|
|
65
|
-
c.onmessage = (
|
|
149
|
+
return clearTimeout(n), t(new Error("SharedWorker port not available"));
|
|
150
|
+
c.onmessage = (h) => {
|
|
66
151
|
try {
|
|
67
|
-
const
|
|
68
|
-
|
|
152
|
+
const d = h.data;
|
|
153
|
+
d && d.type === "CONNECTION_STATUS" && (clearTimeout(n), o = !0, this.workerType = "shared", c.onmessage = null, r(!0));
|
|
69
154
|
} catch {
|
|
70
155
|
}
|
|
71
156
|
};
|
|
@@ -73,30 +158,43 @@ class g {
|
|
|
73
158
|
const e = this.sharedWorkerPort;
|
|
74
159
|
if (e) {
|
|
75
160
|
try {
|
|
76
|
-
const r = {
|
|
161
|
+
const r = {
|
|
162
|
+
type: "INIT",
|
|
163
|
+
backendUrl: this.backendWsUrl
|
|
164
|
+
};
|
|
77
165
|
this.pendingAuthToken && (r.token = this.pendingAuthToken), e.postMessage(r), this.pendingAuthToken = null;
|
|
78
166
|
} catch (r) {
|
|
79
|
-
|
|
167
|
+
i.warn(
|
|
168
|
+
"[WorkerClient] Failed to send INIT to SharedWorker port:",
|
|
169
|
+
r
|
|
170
|
+
);
|
|
80
171
|
}
|
|
81
172
|
e.onmessage = (r) => {
|
|
82
173
|
try {
|
|
83
|
-
const
|
|
84
|
-
if (
|
|
85
|
-
const o = !!
|
|
174
|
+
const t = r.data;
|
|
175
|
+
if (t && t.type === "CONNECTION_STATUS") {
|
|
176
|
+
const o = !!t.connected;
|
|
86
177
|
this.connectionStatusCallbacks.forEach((n) => {
|
|
87
178
|
try {
|
|
88
179
|
n(o);
|
|
89
180
|
} catch {
|
|
90
181
|
}
|
|
91
182
|
});
|
|
92
|
-
}
|
|
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
|
+
);
|
|
93
191
|
} catch {
|
|
94
192
|
}
|
|
95
193
|
};
|
|
96
194
|
}
|
|
97
|
-
return
|
|
195
|
+
return i.info("[WorkerClient] Using SharedWorker"), !0;
|
|
98
196
|
} catch (e) {
|
|
99
|
-
return
|
|
197
|
+
return i.warn(
|
|
100
198
|
"[WorkerClient] SharedWorker not available, falling back to ServiceWorker:",
|
|
101
199
|
e
|
|
102
200
|
), !1;
|
|
@@ -107,68 +205,144 @@ class g {
|
|
|
107
205
|
try {
|
|
108
206
|
const e = await navigator.serviceWorker.getRegistration();
|
|
109
207
|
if (e) {
|
|
110
|
-
this.serviceWorkerRegistration = e, this.workerType = "service",
|
|
208
|
+
this.serviceWorkerRegistration = e, this.workerType = "service", i.info(
|
|
111
209
|
"[WorkerClient] Using existing ServiceWorker registration"
|
|
112
210
|
);
|
|
113
211
|
return;
|
|
114
212
|
}
|
|
115
213
|
this.serviceWorkerRegistration = await navigator.serviceWorker.register(
|
|
116
214
|
this.serviceWorkerUrl
|
|
117
|
-
), this.workerType = "service",
|
|
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)");
|
|
118
238
|
try {
|
|
119
|
-
const r = {
|
|
239
|
+
const r = {
|
|
240
|
+
type: "INIT",
|
|
241
|
+
backendUrl: this.backendWsUrl
|
|
242
|
+
};
|
|
120
243
|
this.pendingAuthToken && (r.token = this.pendingAuthToken), this.serviceWorkerRegistration.active ? this.serviceWorkerRegistration.active.postMessage(r) : "serviceWorker" in navigator && navigator.serviceWorker.controller && navigator.serviceWorker.controller.postMessage(r), this.pendingAuthToken = null;
|
|
121
244
|
} catch {
|
|
122
245
|
}
|
|
123
246
|
} catch (e) {
|
|
124
|
-
throw
|
|
125
|
-
"[WorkerClient] Failed to register ServiceWorker:",
|
|
126
|
-
e
|
|
127
|
-
), e;
|
|
247
|
+
throw i.error("[WorkerClient] Failed to register ServiceWorker:", e), e;
|
|
128
248
|
}
|
|
129
249
|
else
|
|
130
250
|
throw new Error("Neither SharedWorker nor ServiceWorker is supported");
|
|
131
251
|
}
|
|
132
252
|
// Low-level request that expects a reply via MessageChannel
|
|
133
|
-
async request(e, r,
|
|
134
|
-
if (
|
|
253
|
+
async request(e, r, t = 5e3) {
|
|
254
|
+
if (i.log("[WorkerClient] Request started:", {
|
|
255
|
+
type: e,
|
|
256
|
+
payload: r,
|
|
257
|
+
timeoutMs: t,
|
|
258
|
+
workerType: this.workerType,
|
|
259
|
+
hasSharedWorkerPort: !!this.sharedWorkerPort,
|
|
260
|
+
hasServiceWorkerReg: !!this.serviceWorkerRegistration
|
|
261
|
+
}), this.workerType === "shared" && this.sharedWorkerPort)
|
|
135
262
|
return new Promise((o, n) => {
|
|
136
|
-
const c = new MessageChannel(),
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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:", {
|
|
266
|
+
type: e,
|
|
267
|
+
requestId: h,
|
|
268
|
+
elapsed: a,
|
|
269
|
+
timeoutMs: t
|
|
270
|
+
}), c.port1.onmessage = null, n(new Error("Request timeout"));
|
|
271
|
+
}, t);
|
|
272
|
+
c.port1.onmessage = (a) => {
|
|
273
|
+
try {
|
|
274
|
+
const l = Date.now() - d;
|
|
275
|
+
i.log("[WorkerClient] Request response received:", {
|
|
276
|
+
type: e,
|
|
277
|
+
requestId: h,
|
|
278
|
+
elapsed: l,
|
|
279
|
+
success: a.data?.success
|
|
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))
|
|
284
|
+
);
|
|
285
|
+
}
|
|
141
286
|
};
|
|
142
287
|
try {
|
|
143
|
-
const
|
|
144
|
-
if (!
|
|
145
|
-
return clearTimeout(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
288
|
+
const a = this.sharedWorkerPort;
|
|
289
|
+
if (!a)
|
|
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:", {
|
|
292
|
+
type: e,
|
|
293
|
+
requestId: h
|
|
294
|
+
}), a.postMessage({ type: e, ...r || {} }, [c.port2]);
|
|
295
|
+
} catch (a) {
|
|
296
|
+
clearTimeout(g), i.error("[WorkerClient] Failed to post message:", a), n(a instanceof Error ? a : new Error(String(a)));
|
|
149
297
|
}
|
|
150
298
|
});
|
|
151
299
|
if (this.workerType === "service" && this.serviceWorkerRegistration) {
|
|
152
300
|
const o = this.serviceWorkerRegistration;
|
|
153
301
|
if (!o) throw new Error("Service worker registration missing");
|
|
154
|
-
if (!o.active && (await navigator.serviceWorker.ready, !o.active))
|
|
302
|
+
if (!o.active && (i.log("[WorkerClient] ServiceWorker not active, waiting..."), await navigator.serviceWorker.ready, !o.active))
|
|
155
303
|
throw new Error("Service worker not active");
|
|
156
304
|
return new Promise((n, c) => {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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:", {
|
|
308
|
+
type: e,
|
|
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) => {
|
|
315
|
+
try {
|
|
316
|
+
const u = Date.now() - g;
|
|
317
|
+
i.log("[WorkerClient] ServiceWorker response received:", {
|
|
318
|
+
type: e,
|
|
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))
|
|
326
|
+
);
|
|
327
|
+
}
|
|
162
328
|
};
|
|
163
329
|
try {
|
|
164
330
|
const l = o.active;
|
|
165
331
|
if (!l)
|
|
166
|
-
return clearTimeout(
|
|
332
|
+
return clearTimeout(a), i.error(
|
|
333
|
+
"[WorkerClient] ServiceWorker active instance not available"
|
|
334
|
+
), c(
|
|
167
335
|
new Error("Service worker active instance not available")
|
|
168
336
|
);
|
|
169
|
-
|
|
337
|
+
i.log("[WorkerClient] Posting message to ServiceWorker:", {
|
|
338
|
+
type: e,
|
|
339
|
+
requestId: d
|
|
340
|
+
}), l.postMessage({ type: e, ...r || {} }, [h.port2]);
|
|
170
341
|
} catch (l) {
|
|
171
|
-
clearTimeout(
|
|
342
|
+
clearTimeout(a), i.error(
|
|
343
|
+
"[WorkerClient] Failed to post message to ServiceWorker:",
|
|
344
|
+
l
|
|
345
|
+
), c(l instanceof Error ? l : new Error(String(l)));
|
|
172
346
|
}
|
|
173
347
|
});
|
|
174
348
|
}
|
|
@@ -179,8 +353,8 @@ class g {
|
|
|
179
353
|
if (this.workerType === "shared" && this.sharedWorkerPort) {
|
|
180
354
|
try {
|
|
181
355
|
this.sharedWorkerPort.postMessage({ type: e, ...r || {} });
|
|
182
|
-
} catch (
|
|
183
|
-
|
|
356
|
+
} catch (t) {
|
|
357
|
+
i.error("[WorkerClient] Failed to post to SharedWorker:", t);
|
|
184
358
|
}
|
|
185
359
|
return;
|
|
186
360
|
}
|
|
@@ -190,10 +364,10 @@ class g {
|
|
|
190
364
|
type: e,
|
|
191
365
|
...r || {}
|
|
192
366
|
});
|
|
193
|
-
} catch (
|
|
194
|
-
|
|
367
|
+
} catch (t) {
|
|
368
|
+
i.error(
|
|
195
369
|
"[WorkerClient] Failed to post to ServiceWorker (active):",
|
|
196
|
-
|
|
370
|
+
t
|
|
197
371
|
);
|
|
198
372
|
}
|
|
199
373
|
return;
|
|
@@ -204,17 +378,17 @@ class g {
|
|
|
204
378
|
type: e,
|
|
205
379
|
...r || {}
|
|
206
380
|
});
|
|
207
|
-
} catch (
|
|
208
|
-
|
|
381
|
+
} catch (t) {
|
|
382
|
+
i.error(
|
|
209
383
|
"[WorkerClient] Failed to post to ServiceWorker.controller:",
|
|
210
|
-
|
|
384
|
+
t
|
|
211
385
|
);
|
|
212
386
|
}
|
|
213
387
|
return;
|
|
214
388
|
}
|
|
215
389
|
if (e === "SET_AUTH_TOKEN" && r) {
|
|
216
|
-
const
|
|
217
|
-
typeof
|
|
390
|
+
const t = r.token;
|
|
391
|
+
typeof t == "string" && (this.pendingAuthToken = t);
|
|
218
392
|
}
|
|
219
393
|
}
|
|
220
394
|
sendAuthTokenToServiceWorker(e) {
|
|
@@ -269,82 +443,304 @@ class g {
|
|
|
269
443
|
try {
|
|
270
444
|
this.sharedWorkerPort.postMessage({ type: "SET_AUTH_TOKEN", token: e }), this.pendingAuthToken = null;
|
|
271
445
|
} catch (r) {
|
|
272
|
-
|
|
446
|
+
i.error(
|
|
273
447
|
"[WorkerClient] Failed to set auth token on SharedWorker:",
|
|
274
448
|
r
|
|
275
449
|
);
|
|
276
450
|
}
|
|
277
451
|
else this.workerType === "service" && (this.sendAuthTokenToServiceWorker(e), this.pendingAuthToken = null);
|
|
278
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
|
+
}
|
|
279
675
|
}
|
|
280
|
-
const
|
|
281
|
-
async function
|
|
282
|
-
return new Promise((
|
|
283
|
-
const r = indexedDB.open(
|
|
284
|
-
r.onerror = () => e(r.error), r.onsuccess = () =>
|
|
285
|
-
const o =
|
|
286
|
-
if (!o.objectStoreNames.contains(
|
|
287
|
-
const n = o.createObjectStore(
|
|
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" });
|
|
288
684
|
n.createIndex("timestamp", "timestamp", { unique: !1 }), n.createIndex("type", "type", { unique: !1 }), n.createIndex("path", "path", { unique: !1 });
|
|
289
685
|
}
|
|
290
686
|
};
|
|
291
687
|
});
|
|
292
688
|
}
|
|
293
|
-
async function
|
|
294
|
-
const o = (await
|
|
689
|
+
async function m(s) {
|
|
690
|
+
const o = (await S()).transaction([f], "readonly").objectStore(f).index("timestamp");
|
|
295
691
|
return new Promise((n, c) => {
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
let
|
|
299
|
-
|
|
300
|
-
},
|
|
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);
|
|
301
697
|
});
|
|
302
698
|
}
|
|
303
|
-
const
|
|
304
|
-
async function
|
|
305
|
-
return
|
|
699
|
+
const k = new C();
|
|
700
|
+
async function R(s) {
|
|
701
|
+
return k.init(s);
|
|
306
702
|
}
|
|
307
|
-
async function
|
|
308
|
-
const e = { ...
|
|
309
|
-
await
|
|
703
|
+
async function W(s) {
|
|
704
|
+
const e = { ...s, timestamp: Date.now() };
|
|
705
|
+
await k.request("STORE_EVENT", { event: e });
|
|
310
706
|
}
|
|
311
|
-
async function
|
|
312
|
-
const
|
|
313
|
-
return Array.isArray(
|
|
707
|
+
async function E() {
|
|
708
|
+
const s = await m();
|
|
709
|
+
return Array.isArray(s) ? s : [];
|
|
314
710
|
}
|
|
315
|
-
async function
|
|
316
|
-
return
|
|
711
|
+
async function P() {
|
|
712
|
+
return k.getConnectionStatus();
|
|
317
713
|
}
|
|
318
|
-
function
|
|
319
|
-
|
|
714
|
+
function A(s) {
|
|
715
|
+
k.setAuthToken(s);
|
|
320
716
|
}
|
|
321
|
-
function
|
|
322
|
-
|
|
717
|
+
function I(s) {
|
|
718
|
+
k.onConnectionStatus(s);
|
|
323
719
|
}
|
|
324
|
-
function
|
|
325
|
-
|
|
720
|
+
function b(s) {
|
|
721
|
+
k.offConnectionStatus(s);
|
|
326
722
|
}
|
|
327
|
-
async function
|
|
328
|
-
return
|
|
723
|
+
async function U(s, e, r) {
|
|
724
|
+
return W({ type: "navigation", from: s, to: e, path: r || e });
|
|
329
725
|
}
|
|
330
|
-
async function
|
|
331
|
-
const
|
|
332
|
-
return
|
|
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({
|
|
333
729
|
type: "click",
|
|
334
730
|
element: c,
|
|
335
|
-
elementId:
|
|
731
|
+
elementId: t,
|
|
336
732
|
elementClass: o,
|
|
337
733
|
elementText: n,
|
|
338
734
|
path: e || window.location.pathname,
|
|
339
735
|
metadata: r
|
|
340
736
|
});
|
|
341
737
|
}
|
|
342
|
-
async function
|
|
343
|
-
const
|
|
344
|
-
return
|
|
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({
|
|
345
741
|
type: "input",
|
|
346
742
|
element: n,
|
|
347
|
-
elementId:
|
|
743
|
+
elementId: t,
|
|
348
744
|
elementClass: o,
|
|
349
745
|
path: r || window.location.pathname,
|
|
350
746
|
metadata: {
|
|
@@ -353,26 +749,26 @@ async function A(t, e, r) {
|
|
|
353
749
|
}
|
|
354
750
|
});
|
|
355
751
|
}
|
|
356
|
-
async function
|
|
357
|
-
return
|
|
752
|
+
async function _(s, e, r) {
|
|
753
|
+
return W({
|
|
358
754
|
type: "custom",
|
|
359
755
|
path: r || window.location.pathname,
|
|
360
756
|
metadata: {
|
|
361
|
-
eventName:
|
|
757
|
+
eventName: s,
|
|
362
758
|
...e
|
|
363
759
|
}
|
|
364
760
|
});
|
|
365
761
|
}
|
|
366
762
|
export {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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,
|
|
770
|
+
_ as trackCustom,
|
|
771
|
+
W as trackEvent,
|
|
772
|
+
M as trackInput,
|
|
773
|
+
U as trackNavigation
|
|
378
774
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-fe/event-tracker",
|
|
3
|
-
"version": "0.0
|
|
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
|
|
16
|
+
"@mcp-fe/mcp-worker": "0.1.0"
|
|
17
17
|
},
|
|
18
18
|
"publishConfig": {
|
|
19
19
|
"access": "public"
|