@mcp-fe/mcp-worker 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/docs/api.md ADDED
@@ -0,0 +1,418 @@
1
+ # API Reference
2
+
3
+ Complete API documentation for `@mcp-fe/mcp-worker`.
4
+
5
+ ## WorkerClient
6
+
7
+ The main singleton instance for communicating with the MCP worker.
8
+
9
+ ```typescript
10
+ import { workerClient } from '@mcp-fe/mcp-worker';
11
+ ```
12
+
13
+ ### Initialization
14
+
15
+ #### `workerClient.init(options?)`
16
+
17
+ Initializes the worker client with optional configuration.
18
+
19
+ **Parameters:**
20
+ - `options?: WorkerClientInitOptions | ServiceWorkerRegistration`
21
+
22
+ **WorkerClientInitOptions:**
23
+ ```typescript
24
+ interface WorkerClientInitOptions {
25
+ sharedWorkerUrl?: string; // Default: '/mcp-shared-worker.js'
26
+ serviceWorkerUrl?: string; // Default: '/mcp-service-worker.js'
27
+ backendWsUrl?: string; // Default: 'ws://localhost:3001'
28
+ }
29
+ ```
30
+
31
+ **Returns:** `Promise<void>`
32
+
33
+ **Examples:**
34
+ ```typescript
35
+ // Basic initialization with defaults
36
+ await workerClient.init();
37
+
38
+ // Custom configuration
39
+ await workerClient.init({
40
+ backendWsUrl: 'wss://my-mcp-proxy.com/ws',
41
+ sharedWorkerUrl: '/workers/mcp-shared-worker.js'
42
+ });
43
+
44
+ // Use existing ServiceWorker registration
45
+ const registration = await navigator.serviceWorker.register('/mcp-service-worker.js');
46
+ await workerClient.init(registration);
47
+ ```
48
+
49
+ ### Event Storage
50
+
51
+ #### `workerClient.post(type, payload?)`
52
+
53
+ Send a fire-and-forget message to the worker.
54
+
55
+ **Parameters:**
56
+ - `type: string` - Message type
57
+ - `payload?: Record<string, unknown>` - Message payload
58
+
59
+ **Returns:** `Promise<void>`
60
+
61
+ **Example:**
62
+ ```typescript
63
+ // Store a user event
64
+ await workerClient.post('STORE_EVENT', {
65
+ event: {
66
+ type: 'click',
67
+ element: 'button',
68
+ elementText: 'Submit Form',
69
+ path: '/checkout',
70
+ timestamp: Date.now()
71
+ }
72
+ });
73
+ ```
74
+
75
+ #### `workerClient.request(type, payload?, timeoutMs?)`
76
+
77
+ Send a request expecting a response via MessageChannel.
78
+
79
+ **Parameters:**
80
+ - `type: string` - Request type
81
+ - `payload?: Record<string, unknown>` - Request payload
82
+ - `timeoutMs?: number` - Timeout in milliseconds (default: 5000)
83
+
84
+ **Returns:** `Promise<T>` - Response data
85
+
86
+ **Example:**
87
+ ```typescript
88
+ // Get stored events
89
+ const response = await workerClient.request('GET_EVENTS', {
90
+ type: 'click',
91
+ limit: 10
92
+ });
93
+
94
+ console.log('Recent clicks:', response.events);
95
+ ```
96
+
97
+ ### Authentication
98
+
99
+ #### `workerClient.setAuthToken(token)`
100
+
101
+ Set authentication token for the current session.
102
+
103
+ **Parameters:**
104
+ - `token: string` - Authentication token (e.g., JWT)
105
+
106
+ **Example:**
107
+ ```typescript
108
+ // Set token after user login
109
+ workerClient.setAuthToken('Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6...');
110
+
111
+ // Clear token on logout
112
+ workerClient.setAuthToken('');
113
+ ```
114
+
115
+ ### Connection Status
116
+
117
+ #### `workerClient.getConnectionStatus()`
118
+
119
+ Get current connection status to the MCP proxy server.
120
+
121
+ **Returns:** `Promise<boolean>` - Connection status
122
+
123
+ **Example:**
124
+ ```typescript
125
+ const isConnected = await workerClient.getConnectionStatus();
126
+ console.log('MCP connection:', isConnected ? 'Connected' : 'Disconnected');
127
+ ```
128
+
129
+ #### `workerClient.onConnectionStatus(callback)`
130
+
131
+ Subscribe to connection status changes.
132
+
133
+ **Parameters:**
134
+ - `callback: (connected: boolean) => void` - Status change callback
135
+
136
+ **Example:**
137
+ ```typescript
138
+ const handleConnectionChange = (connected: boolean) => {
139
+ console.log('Connection status:', connected ? 'Connected' : 'Disconnected');
140
+ };
141
+
142
+ workerClient.onConnectionStatus(handleConnectionChange);
143
+ ```
144
+
145
+ #### `workerClient.offConnectionStatus(callback)`
146
+
147
+ Unsubscribe from connection status changes.
148
+
149
+ **Parameters:**
150
+ - `callback: (connected: boolean) => void` - Previously registered callback
151
+
152
+ **Example:**
153
+ ```typescript
154
+ workerClient.offConnectionStatus(handleConnectionChange);
155
+ ```
156
+
157
+ ### Dynamic Tool Registration
158
+
159
+ #### `workerClient.registerTool(name, description, inputSchema, handler)`
160
+
161
+ Register a custom MCP tool dynamically.
162
+
163
+ **Parameters:**
164
+ - `name: string` - Tool name (use snake_case)
165
+ - `description: string` - Tool description (AI uses this)
166
+ - `inputSchema: Record<string, unknown>` - JSON Schema for input validation
167
+ - `handler: (args: unknown) => Promise<ToolResult>` - Async handler function
168
+
169
+ **Returns:** `Promise<void>`
170
+
171
+ **Example:**
172
+ ```typescript
173
+ await workerClient.registerTool(
174
+ 'get_user_data',
175
+ 'Get current user information',
176
+ { type: 'object', properties: {} },
177
+ async () => {
178
+ const user = getCurrentUser();
179
+ return {
180
+ content: [{
181
+ type: 'text',
182
+ text: JSON.stringify(user)
183
+ }]
184
+ };
185
+ }
186
+ );
187
+ ```
188
+
189
+ See [Dynamic Tool Registration Guide](./guide.md) for complete documentation.
190
+
191
+ #### `workerClient.unregisterTool(name)`
192
+
193
+ Unregister a previously registered tool.
194
+
195
+ **Parameters:**
196
+ - `name: string` - Tool name to unregister
197
+
198
+ **Returns:** `Promise<boolean>` - True if tool was found and removed
199
+
200
+ **Example:**
201
+ ```typescript
202
+ const success = await workerClient.unregisterTool('get_user_data');
203
+ ```
204
+
205
+ #### `workerClient.onToolChange(name, callback)`
206
+
207
+ Subscribe to tool registration changes (for reactive updates).
208
+
209
+ **Parameters:**
210
+ - `name: string` - Tool name to watch
211
+ - `callback: (info: ToolInfo | null) => void` - Change callback
212
+
213
+ **Returns:** `() => void` - Unsubscribe function
214
+
215
+ **Example:**
216
+ ```typescript
217
+ const unsubscribe = workerClient.onToolChange('my_tool', (info) => {
218
+ console.log('Tool info:', info);
219
+ });
220
+
221
+ // Later: unsubscribe
222
+ unsubscribe();
223
+ ```
224
+
225
+ #### `workerClient.getToolInfo(name)`
226
+
227
+ Get information about a registered tool.
228
+
229
+ **Parameters:**
230
+ - `name: string` - Tool name
231
+
232
+ **Returns:** `ToolInfo | null`
233
+
234
+ ```typescript
235
+ interface ToolInfo {
236
+ refCount: number;
237
+ isRegistered: boolean;
238
+ }
239
+ ```
240
+
241
+ **Example:**
242
+ ```typescript
243
+ const info = workerClient.getToolInfo('my_tool');
244
+ if (info) {
245
+ console.log('RefCount:', info.refCount);
246
+ console.log('Registered:', info.isRegistered);
247
+ }
248
+ ```
249
+
250
+ #### `workerClient.isToolRegistered(name)`
251
+
252
+ Check if a tool is registered.
253
+
254
+ **Parameters:**
255
+ - `name: string` - Tool name
256
+
257
+ **Returns:** `boolean`
258
+
259
+ **Example:**
260
+ ```typescript
261
+ if (workerClient.isToolRegistered('my_tool')) {
262
+ console.log('Tool is registered');
263
+ }
264
+ ```
265
+
266
+ #### `workerClient.getRegisteredTools()`
267
+
268
+ Get all registered tool names.
269
+
270
+ **Returns:** `string[]`
271
+
272
+ **Example:**
273
+ ```typescript
274
+ const tools = workerClient.getRegisteredTools();
275
+ console.log('Registered tools:', tools);
276
+ ```
277
+
278
+ ### Worker State
279
+
280
+ #### `workerClient.initialized`
281
+
282
+ Check if the worker is initialized.
283
+
284
+ **Type:** `boolean` (getter)
285
+
286
+ **Example:**
287
+ ```typescript
288
+ if (workerClient.initialized) {
289
+ console.log('Worker is ready');
290
+ }
291
+ ```
292
+
293
+ #### `workerClient.waitForInit()`
294
+
295
+ Wait for worker initialization to complete.
296
+
297
+ **Returns:** `Promise<void>`
298
+
299
+ **Example:**
300
+ ```typescript
301
+ await workerClient.waitForInit();
302
+ console.log('Worker is now initialized');
303
+ ```
304
+
305
+ ## Type Definitions
306
+
307
+ ### ToolResult
308
+
309
+ ```typescript
310
+ interface ToolResult {
311
+ content: Array<{
312
+ type: 'text' | 'image' | 'resource';
313
+ text?: string;
314
+ data?: string;
315
+ mimeType?: string;
316
+ }>;
317
+ }
318
+ ```
319
+
320
+ ### ToolHandler
321
+
322
+ ```typescript
323
+ type ToolHandler = (args: unknown) => Promise<ToolResult>;
324
+ ```
325
+
326
+ ### UserEvent
327
+
328
+ ```typescript
329
+ interface UserEvent {
330
+ id: string;
331
+ type: 'navigation' | 'click' | 'input' | 'custom';
332
+ timestamp: number;
333
+ path?: string;
334
+ from?: string; // navigation: previous route
335
+ to?: string; // navigation: current route
336
+ element?: string; // interaction: element tag
337
+ elementId?: string; // interaction: element ID
338
+ elementClass?: string; // interaction: element classes
339
+ elementText?: string; // interaction: element text content
340
+ metadata?: Record<string, unknown>;
341
+ }
342
+ ```
343
+
344
+ ## Advanced Usage
345
+
346
+ ### Custom Event Storage
347
+
348
+ ```typescript
349
+ // Store custom business events
350
+ await workerClient.post('STORE_EVENT', {
351
+ event: {
352
+ type: 'custom',
353
+ timestamp: Date.now(),
354
+ metadata: {
355
+ eventName: 'purchase_completed',
356
+ orderId: '12345',
357
+ amount: 99.99,
358
+ currency: 'USD'
359
+ }
360
+ }
361
+ });
362
+ ```
363
+
364
+ ### Querying Specific Events
365
+
366
+ ```typescript
367
+ // Get navigation events from the last hour
368
+ const response = await workerClient.request('GET_EVENTS', {
369
+ type: 'navigation',
370
+ startTime: Date.now() - (60 * 60 * 1000),
371
+ limit: 50
372
+ });
373
+
374
+ // Get clicks on specific elements
375
+ const clicks = await workerClient.request('GET_EVENTS', {
376
+ type: 'click',
377
+ path: '/checkout',
378
+ limit: 20
379
+ });
380
+ ```
381
+
382
+ ### Error Handling
383
+
384
+ ```typescript
385
+ try {
386
+ await workerClient.init();
387
+ } catch (error) {
388
+ if (error.message.includes('SharedWorker')) {
389
+ console.log('SharedWorker not supported, using ServiceWorker fallback');
390
+ } else {
391
+ console.error('Worker initialization failed:', error);
392
+ }
393
+ }
394
+
395
+ // Handle request timeouts
396
+ try {
397
+ const data = await workerClient.request('GET_EVENTS', {}, 2000); // 2s timeout
398
+ } catch (error) {
399
+ if (error.message.includes('timeout')) {
400
+ console.log('Request timed out');
401
+ }
402
+ }
403
+ ```
404
+
405
+ ## MCP Tools Exposed
406
+
407
+ The worker exposes these MCP tools to AI agents:
408
+
409
+ - `get_user_events` - Query stored user interaction events
410
+ - `get_connection_status` - Check WebSocket connection status
411
+ - `get_session_info` - Get current session information
412
+ - Custom tools registered via `registerTool()`
413
+
414
+ ## See Also
415
+
416
+ - [Guide](./guide.md) - Dynamic tool registration guide
417
+ - [Worker Implementation Details](./worker-details.md) - Technical details
418
+ - [Architecture](./architecture.md) - How it works
@@ -0,0 +1,195 @@
1
+ # Proxy Architecture
2
+
3
+ How dynamic tool registration works with handlers running in the main thread.
4
+
5
+ ## Overview
6
+
7
+ The library uses a **proxy pattern** where:
8
+ - ✅ Handlers run in **main thread** (browser context)
9
+ - ✅ Worker acts as **proxy** between MCP and handlers
10
+ - ✅ **No serialization** of function code
11
+ - ✅ **Full access** to browser APIs, React, imports, etc.
12
+
13
+ ## Architecture
14
+
15
+ ```
16
+ ┌─────────────────────┐
17
+ │ MCP Client │
18
+ │ (Claude, etc.) │
19
+ └──────────┬──────────┘
20
+ │ MCP Protocol
21
+
22
+ ┌─────────────────────┐
23
+ │ Shared/Service │
24
+ │ Worker (MCP Server)│
25
+ │ │
26
+ │ Tool Registry │
27
+ │ ├─ Proxy Handler │ ← metadata + proxy
28
+ │ └─ postMessage │
29
+ └──────────┬──────────┘
30
+ │ postMessage({ type: 'CALL_TOOL', args, callId })
31
+
32
+ ┌─────────────────────┐
33
+ │ Main Thread │
34
+ │ (Browser Context) │
35
+ │ │
36
+ │ WorkerClient │
37
+ │ ├─ toolHandlers │ ← actual handler functions
38
+ │ └─ execute │ ← with full API access
39
+ └──────────┬──────────┘
40
+ │ postMessage({ type: 'TOOL_CALL_RESULT', result })
41
+
42
+ ┌─────────────────────┐
43
+ │ Worker │
44
+ │ ├─ resolve Promise │
45
+ │ └─ return to MCP │
46
+ └─────────────────────┘
47
+ ```
48
+
49
+ ## Benefits
50
+
51
+ ### No Serialization Issues
52
+ - Handlers are normal functions in main thread
53
+ - No `.toString()` → `new Function()` conversion
54
+ - All closures and imports preserved
55
+
56
+ ### Full Browser API Access
57
+ ```typescript
58
+ await client.registerTool('get_page_info', '...', {}, async () => {
59
+ // ✅ DOM access
60
+ const title = document.title;
61
+
62
+ // ✅ localStorage
63
+ const theme = localStorage.getItem('theme');
64
+
65
+ // ✅ React hooks/context (if handler in component)
66
+ const user = useUser();
67
+
68
+ return { content: [{ type: 'text', text: JSON.stringify({ title, theme, user }) }] };
69
+ });
70
+ ```
71
+
72
+ ### Use Any Imports
73
+ ```typescript
74
+ import { z } from 'zod';
75
+ import { myApi } from './api';
76
+
77
+ await client.registerTool('validate', '...', schema, async (args: any) => {
78
+ // ✅ Use any imports!
79
+ const validated = z.object({ ... }).parse(args);
80
+ const result = await myApi.callSomething(validated);
81
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
82
+ });
83
+ ```
84
+
85
+ ### Easy Testing
86
+ ```typescript
87
+ // Handler is a normal async function
88
+ const myHandler = async (args: any) => {
89
+ // ... logic ...
90
+ return { content: [{ type: 'text', text: '...' }] };
91
+ };
92
+
93
+ // Test directly
94
+ test('myHandler works', async () => {
95
+ const result = await myHandler({ test: 'data' });
96
+ expect(result.content[0].text).toContain('data');
97
+ });
98
+
99
+ // Then register
100
+ await client.registerTool('my_tool', '...', schema, myHandler);
101
+ ```
102
+
103
+ ## Implementation
104
+
105
+ ### WorkerClient (Main Thread)
106
+
107
+ Stores handlers locally and executes them when called:
108
+
109
+ ```typescript
110
+ private toolHandlers = new Map<string, HandlerFunction>();
111
+
112
+ public async registerTool(name, description, schema, handler) {
113
+ // Store handler in main thread
114
+ this.toolHandlers.set(name, handler);
115
+
116
+ // Tell worker to create proxy
117
+ await this.request('REGISTER_TOOL', {
118
+ name, description, inputSchema: schema,
119
+ handlerType: 'proxy' // ← important!
120
+ });
121
+ }
122
+
123
+ private async handleToolCall(toolName: string, args: unknown, callId: string) {
124
+ try {
125
+ const handler = this.toolHandlers.get(toolName);
126
+ const result = await handler(args); // ← runs in main thread!
127
+
128
+ this.sendToolCallResult(callId, { success: true, result });
129
+ } catch (error) {
130
+ this.sendToolCallResult(callId, { success: false, error: error.message });
131
+ }
132
+ }
133
+ ```
134
+
135
+ ### MCPController (Worker)
136
+
137
+ Creates proxy handler and forwards calls:
138
+
139
+ ```typescript
140
+ public async handleRegisterTool(toolData: Record<string, unknown>) {
141
+ const { name, description, inputSchema, handlerType } = toolData;
142
+
143
+ if (handlerType === 'proxy') {
144
+ // Create proxy handler that forwards to main thread
145
+ mcpServer.registerTool(name, description, inputSchema,
146
+ async (args: unknown) => {
147
+ // Forward to main thread
148
+ const callId = generateId();
149
+ this.broadcast({ type: 'CALL_TOOL', toolName: name, args, callId });
150
+
151
+ // Wait for result
152
+ return await this.waitForToolCallResult(callId);
153
+ }
154
+ );
155
+ }
156
+ }
157
+ ```
158
+
159
+ ## Message Flow
160
+
161
+ 1. **MCP Client** calls tool via MCP protocol
162
+ 2. **Worker** receives MCP tool call
163
+ 3. **Worker** sends `CALL_TOOL` message to main thread
164
+ 4. **Main Thread** executes handler function
165
+ 5. **Main Thread** sends `TOOL_CALL_RESULT` back
166
+ 6. **Worker** resolves promise and returns to MCP
167
+ 7. **MCP Client** receives result
168
+
169
+ ## Why This Works
170
+
171
+ - **Worker**: Runs 24/7, maintains MCP connection
172
+ - **Main Thread**: Has full browser API access
173
+ - **Messages**: Bridge between the two contexts
174
+ - **Handlers**: Execute where they have access to everything
175
+
176
+ ## Trade-offs
177
+
178
+ ### Advantages
179
+ - ✅ Full browser API access
180
+ - ✅ No serialization issues
181
+ - ✅ Easy to implement
182
+ - ✅ Easy to test
183
+ - ✅ Works with any library
184
+
185
+ ### Considerations
186
+ - Message passing overhead (minimal, ~1-2ms)
187
+ - Handler must be async (already required by MCP)
188
+
189
+ ## Usage
190
+
191
+ See [Guide](./guide.md) for complete usage guide and examples.
192
+
193
+ See [examples/](../examples/) for runnable code examples.
194
+
195
+ ```