@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.
- package/index.js +490 -180
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
const
|
|
2
|
-
log: (...
|
|
3
|
-
|
|
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: (...
|
|
6
|
-
|
|
5
|
+
debug: (...s) => {
|
|
6
|
+
v && console.debug(...s);
|
|
7
7
|
},
|
|
8
|
-
info: (...
|
|
9
|
-
console.info(...
|
|
8
|
+
info: (...s) => {
|
|
9
|
+
console.info(...s);
|
|
10
10
|
},
|
|
11
|
-
error: (...
|
|
12
|
-
console.error(...
|
|
11
|
+
error: (...s) => {
|
|
12
|
+
console.error(...s);
|
|
13
13
|
},
|
|
14
|
-
warn: (...
|
|
15
|
-
|
|
14
|
+
warn: (...s) => {
|
|
15
|
+
v && console.warn(...s);
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
|
-
class
|
|
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
|
-
|
|
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
|
|
44
|
-
if (
|
|
45
|
-
r = e,
|
|
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
|
|
48
|
-
|
|
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",
|
|
66
|
+
this.serviceWorkerRegistration = r, this.workerType = "service", i.info(
|
|
56
67
|
"[WorkerClient] Using ServiceWorker (explicit registration)"
|
|
57
68
|
);
|
|
58
69
|
try {
|
|
59
|
-
const
|
|
70
|
+
const n = {
|
|
60
71
|
type: "INIT",
|
|
61
72
|
backendUrl: this.backendWsUrl
|
|
62
73
|
};
|
|
63
|
-
this.pendingAuthToken && (
|
|
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())
|
|
69
|
-
|
|
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,
|
|
82
|
-
let
|
|
83
|
-
const
|
|
84
|
-
if (!
|
|
85
|
-
const
|
|
86
|
-
|
|
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),
|
|
89
|
-
if (!
|
|
90
|
-
return clearTimeout(
|
|
91
|
-
|
|
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
|
|
94
|
-
|
|
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
|
-
|
|
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
|
|
116
|
-
if (
|
|
117
|
-
const
|
|
118
|
-
this.connectionStatusCallbacks.forEach((
|
|
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
|
-
|
|
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
|
|
195
|
+
return i.info("[WorkerClient] Using SharedWorker"), !0;
|
|
130
196
|
} catch (e) {
|
|
131
|
-
return
|
|
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",
|
|
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",
|
|
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
|
|
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,
|
|
166
|
-
if (
|
|
253
|
+
async request(e, r, t = 5e3) {
|
|
254
|
+
if (i.log("[WorkerClient] Request started:", {
|
|
167
255
|
type: e,
|
|
168
256
|
payload: r,
|
|
169
|
-
timeoutMs:
|
|
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((
|
|
175
|
-
const
|
|
176
|
-
const a = Date.now() -
|
|
177
|
-
|
|
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:
|
|
267
|
+
requestId: h,
|
|
180
268
|
elapsed: a,
|
|
181
|
-
timeoutMs:
|
|
182
|
-
}),
|
|
183
|
-
},
|
|
184
|
-
|
|
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
|
|
187
|
-
|
|
274
|
+
const l = Date.now() - d;
|
|
275
|
+
i.log("[WorkerClient] Request response received:", {
|
|
188
276
|
type: e,
|
|
189
|
-
requestId:
|
|
190
|
-
elapsed:
|
|
277
|
+
requestId: h,
|
|
278
|
+
elapsed: l,
|
|
191
279
|
success: a.data?.success
|
|
192
|
-
}), clearTimeout(
|
|
193
|
-
} catch (
|
|
194
|
-
clearTimeout(
|
|
195
|
-
|
|
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(
|
|
203
|
-
|
|
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:
|
|
206
|
-
}), a.postMessage({ type: e, ...r || {} }, [
|
|
293
|
+
requestId: h
|
|
294
|
+
}), a.postMessage({ type: e, ...r || {} }, [c.port2]);
|
|
207
295
|
} catch (a) {
|
|
208
|
-
clearTimeout(
|
|
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
|
|
213
|
-
if (!
|
|
214
|
-
if (!
|
|
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((
|
|
217
|
-
const
|
|
218
|
-
const
|
|
219
|
-
|
|
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:
|
|
222
|
-
elapsed:
|
|
223
|
-
timeoutMs:
|
|
224
|
-
}),
|
|
225
|
-
},
|
|
226
|
-
|
|
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
|
|
229
|
-
|
|
316
|
+
const u = Date.now() - g;
|
|
317
|
+
i.log("[WorkerClient] ServiceWorker response received:", {
|
|
230
318
|
type: e,
|
|
231
|
-
requestId:
|
|
232
|
-
elapsed:
|
|
233
|
-
success:
|
|
234
|
-
}), clearTimeout(a),
|
|
235
|
-
} catch (
|
|
236
|
-
clearTimeout(a),
|
|
237
|
-
|
|
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
|
|
243
|
-
if (!
|
|
244
|
-
return clearTimeout(a),
|
|
330
|
+
const l = o.active;
|
|
331
|
+
if (!l)
|
|
332
|
+
return clearTimeout(a), i.error(
|
|
245
333
|
"[WorkerClient] ServiceWorker active instance not available"
|
|
246
|
-
),
|
|
334
|
+
), c(
|
|
247
335
|
new Error("Service worker active instance not available")
|
|
248
336
|
);
|
|
249
|
-
|
|
337
|
+
i.log("[WorkerClient] Posting message to ServiceWorker:", {
|
|
250
338
|
type: e,
|
|
251
|
-
requestId:
|
|
252
|
-
}),
|
|
253
|
-
} catch (
|
|
254
|
-
clearTimeout(a),
|
|
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
|
-
|
|
257
|
-
), l
|
|
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 (
|
|
269
|
-
|
|
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 (
|
|
280
|
-
|
|
367
|
+
} catch (t) {
|
|
368
|
+
i.error(
|
|
281
369
|
"[WorkerClient] Failed to post to ServiceWorker (active):",
|
|
282
|
-
|
|
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 (
|
|
294
|
-
|
|
381
|
+
} catch (t) {
|
|
382
|
+
i.error(
|
|
295
383
|
"[WorkerClient] Failed to post to ServiceWorker.controller:",
|
|
296
|
-
|
|
384
|
+
t
|
|
297
385
|
);
|
|
298
386
|
}
|
|
299
387
|
return;
|
|
300
388
|
}
|
|
301
389
|
if (e === "SET_AUTH_TOKEN" && r) {
|
|
302
|
-
const
|
|
303
|
-
typeof
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
367
|
-
async function
|
|
368
|
-
return new Promise((
|
|
369
|
-
const r = indexedDB.open(
|
|
370
|
-
r.onerror = () => e(r.error), r.onsuccess = () =>
|
|
371
|
-
const
|
|
372
|
-
if (!
|
|
373
|
-
const
|
|
374
|
-
|
|
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
|
|
380
|
-
const
|
|
381
|
-
return new Promise((
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
let
|
|
385
|
-
|
|
386
|
-
},
|
|
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
|
|
390
|
-
async function
|
|
391
|
-
return
|
|
699
|
+
const k = new C();
|
|
700
|
+
async function R(s) {
|
|
701
|
+
return k.init(s);
|
|
392
702
|
}
|
|
393
|
-
async function
|
|
394
|
-
const e = { ...
|
|
395
|
-
await
|
|
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
|
|
398
|
-
const
|
|
399
|
-
return Array.isArray(
|
|
707
|
+
async function E() {
|
|
708
|
+
const s = await m();
|
|
709
|
+
return Array.isArray(s) ? s : [];
|
|
400
710
|
}
|
|
401
|
-
async function
|
|
402
|
-
return
|
|
711
|
+
async function P() {
|
|
712
|
+
return k.getConnectionStatus();
|
|
403
713
|
}
|
|
404
|
-
function
|
|
405
|
-
|
|
714
|
+
function A(s) {
|
|
715
|
+
k.setAuthToken(s);
|
|
406
716
|
}
|
|
407
|
-
function
|
|
408
|
-
|
|
717
|
+
function I(s) {
|
|
718
|
+
k.onConnectionStatus(s);
|
|
409
719
|
}
|
|
410
|
-
function
|
|
411
|
-
|
|
720
|
+
function b(s) {
|
|
721
|
+
k.offConnectionStatus(s);
|
|
412
722
|
}
|
|
413
|
-
async function
|
|
414
|
-
return
|
|
723
|
+
async function U(s, e, r) {
|
|
724
|
+
return W({ type: "navigation", from: s, to: e, path: r || e });
|
|
415
725
|
}
|
|
416
|
-
async function
|
|
417
|
-
const
|
|
418
|
-
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({
|
|
419
729
|
type: "click",
|
|
420
|
-
element:
|
|
421
|
-
elementId:
|
|
422
|
-
elementClass:
|
|
423
|
-
elementText:
|
|
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(
|
|
429
|
-
const
|
|
430
|
-
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({
|
|
431
741
|
type: "input",
|
|
432
|
-
element:
|
|
433
|
-
elementId:
|
|
434
|
-
elementClass:
|
|
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 _(
|
|
443
|
-
return
|
|
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:
|
|
757
|
+
eventName: s,
|
|
448
758
|
...e
|
|
449
759
|
}
|
|
450
760
|
});
|
|
451
761
|
}
|
|
452
762
|
export {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
771
|
+
W as trackEvent,
|
|
462
772
|
M as trackInput,
|
|
463
|
-
|
|
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
|
|
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"
|