@mcp-web/client 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/dist/client.js ADDED
@@ -0,0 +1,663 @@
1
+ #!/usr/bin/env node
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _MCPWebClient_config, _MCPWebClient_server, _MCPWebClient_query, _MCPWebClient_isDone;
14
+ import { ClientNotConextualizedErrorCode, InvalidAuthenticationErrorCode, MissingAuthenticationErrorCode, QueryDoneErrorCode, QueryNotActiveErrorCode, QueryNotFoundErrorCode, QuerySchema, } from '@mcp-web/types';
15
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
16
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
17
+ import { CallToolRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
18
+ import { JsonRpcRequestSchema, JsonRpcResponseSchema, MCPWebClientConfigSchema, } from './schemas.js';
19
+ function isFatalError(result) {
20
+ return 'errorIsFatal' in result && result.errorIsFatal === true;
21
+ }
22
+ /**
23
+ * MCP client that connects AI agents (like Claude Desktop) to the bridge server.
24
+ *
25
+ * MCPWebClient implements the MCP protocol and can run as a stdio server for
26
+ * AI host applications, or be used programmatically in agent server code.
27
+ *
28
+ * @example Running as MCP server for Claude Desktop
29
+ * ```typescript
30
+ * const client = new MCPWebClient({
31
+ * serverUrl: 'http://localhost:3001',
32
+ * authToken: 'your-auth-token',
33
+ * });
34
+ * await client.run(); // Starts stdio transport
35
+ * ```
36
+ *
37
+ * @example Programmatic usage in agent code
38
+ * ```typescript
39
+ * const client = new MCPWebClient({
40
+ * serverUrl: 'http://localhost:3001',
41
+ * authToken: 'your-auth-token',
42
+ * });
43
+ *
44
+ * // List available tools
45
+ * const { tools } = await client.listTools();
46
+ *
47
+ * // Call a tool
48
+ * const result = await client.callTool('get_todos');
49
+ * ```
50
+ *
51
+ * @example With query context (for agent servers)
52
+ * ```typescript
53
+ * const contextualClient = client.contextualize(query);
54
+ * const result = await contextualClient.callTool('update_todo', { id: '1' });
55
+ * await contextualClient.complete('Todo updated successfully');
56
+ * ```
57
+ */
58
+ export class MCPWebClient {
59
+ /**
60
+ * Creates a new MCPWebClient instance.
61
+ *
62
+ * @param config - Client configuration with server URL and auth token
63
+ * @param query - Optional query for contextualized instances (internal use)
64
+ */
65
+ constructor(config, query) {
66
+ _MCPWebClient_config.set(this, void 0);
67
+ _MCPWebClient_server.set(this, void 0);
68
+ _MCPWebClient_query.set(this, void 0);
69
+ _MCPWebClient_isDone.set(this, false); // Track if query has been completed
70
+ __classPrivateFieldSet(this, _MCPWebClient_config, MCPWebClientConfigSchema.parse(config), "f");
71
+ if (query) {
72
+ __classPrivateFieldSet(this, _MCPWebClient_query, QuerySchema.parse(query), "f");
73
+ }
74
+ }
75
+ getMetaParams(sessionId) {
76
+ if (sessionId || __classPrivateFieldGet(this, _MCPWebClient_query, "f")?.uuid) {
77
+ const meta = {};
78
+ if (sessionId) {
79
+ meta.sessionId = sessionId;
80
+ }
81
+ if (__classPrivateFieldGet(this, _MCPWebClient_query, "f")?.uuid) {
82
+ meta.queryId = __classPrivateFieldGet(this, _MCPWebClient_query, "f").uuid;
83
+ }
84
+ return meta;
85
+ }
86
+ return undefined;
87
+ }
88
+ getParams(sessionId) {
89
+ const meta = this.getMetaParams(sessionId);
90
+ if (meta) {
91
+ return { _meta: meta };
92
+ }
93
+ return undefined;
94
+ }
95
+ async makeToolCallRequest(request) {
96
+ try {
97
+ const { name, arguments: args, _meta } = request.params;
98
+ const response = await this.makeRequest('tools/call', {
99
+ name,
100
+ arguments: args || {},
101
+ ...(_meta && { _meta })
102
+ });
103
+ // Check if response is already in CallToolResult format (from bridge)
104
+ // This happens when the bridge wraps results for Remote MCP compatibility
105
+ if (response &&
106
+ typeof response === 'object' &&
107
+ 'content' in response &&
108
+ Array.isArray(response.content)) {
109
+ return response;
110
+ }
111
+ // Handle different response formats (legacy/unwrapped responses)
112
+ // Check if this is an error response from bridge
113
+ if (response && typeof response === 'object' && 'error' in response) {
114
+ return {
115
+ content: [
116
+ {
117
+ type: 'text',
118
+ text: JSON.stringify(response, null, 2),
119
+ }
120
+ ],
121
+ isError: true
122
+ };
123
+ }
124
+ // Handle successful responses
125
+ // The response could be:
126
+ // 1. A wrapped response: { data: <actual data> }
127
+ // 2. Direct tool result: any type (string, number, object, etc.)
128
+ let content;
129
+ let topLevelMeta;
130
+ let actualData = (response && typeof response === 'object' && 'data' in response)
131
+ ? response.data
132
+ : response;
133
+ // Extract _meta from the data to place at the top level of CallToolResult.
134
+ // The MCP protocol expects _meta as a top-level field on the result object,
135
+ // not embedded inside the JSON text content.
136
+ if (actualData && typeof actualData === 'object' && '_meta' in actualData) {
137
+ const { _meta: extractedMeta, ...rest } = actualData;
138
+ if (extractedMeta && typeof extractedMeta === 'object') {
139
+ topLevelMeta = extractedMeta;
140
+ }
141
+ actualData = rest;
142
+ }
143
+ if (typeof actualData === 'string') {
144
+ // Check if it's a data URL (image)
145
+ if (actualData.startsWith('data:image/')) {
146
+ content = [
147
+ {
148
+ type: 'image',
149
+ data: actualData.split(',')[1],
150
+ mimeType: actualData.split(';')[0].split(':')[1],
151
+ },
152
+ ];
153
+ }
154
+ else {
155
+ content = [
156
+ {
157
+ type: 'text',
158
+ text: actualData
159
+ }
160
+ ];
161
+ }
162
+ }
163
+ else if (actualData !== null && actualData !== undefined) {
164
+ content = [
165
+ {
166
+ type: 'text',
167
+ text: typeof actualData === 'object' ? JSON.stringify(actualData, null, 2) : String(actualData)
168
+ }
169
+ ];
170
+ }
171
+ else {
172
+ // null or undefined result
173
+ content = [
174
+ {
175
+ type: 'text',
176
+ text: ''
177
+ }
178
+ ];
179
+ }
180
+ const callToolResult = { content };
181
+ if (topLevelMeta) {
182
+ callToolResult._meta = topLevelMeta;
183
+ }
184
+ return callToolResult;
185
+ }
186
+ catch (error) {
187
+ // Re-throw authentication and query errors
188
+ if (error instanceof Error) {
189
+ const errorMessage = error.message;
190
+ if (errorMessage === MissingAuthenticationErrorCode ||
191
+ errorMessage === InvalidAuthenticationErrorCode ||
192
+ errorMessage === QueryNotFoundErrorCode ||
193
+ errorMessage === QueryNotActiveErrorCode) {
194
+ throw error;
195
+ }
196
+ }
197
+ // All other errors get returned as CallToolResult with isError: true
198
+ return {
199
+ content: [
200
+ {
201
+ type: 'text',
202
+ text: `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`
203
+ }
204
+ ],
205
+ isError: true
206
+ };
207
+ }
208
+ }
209
+ async makeListToolsRequest(sessionId) {
210
+ const response = await this.makeRequest('tools/list', this.getParams(sessionId));
211
+ if (isFatalError(response)) {
212
+ throw new Error(response.error_message);
213
+ }
214
+ return response;
215
+ }
216
+ async makeListResourcesRequest(sessionId) {
217
+ const response = await this.makeRequest('resources/list', this.getParams(sessionId));
218
+ if (isFatalError(response)) {
219
+ throw new Error(response.error_message);
220
+ }
221
+ return response;
222
+ }
223
+ async makeListPromptsRequest(sessionId) {
224
+ const response = await this.makeRequest('prompts/list', this.getParams(sessionId));
225
+ if (isFatalError(response)) {
226
+ throw new Error(response.error_message);
227
+ }
228
+ return response;
229
+ }
230
+ async makeReadResourceRequest(request) {
231
+ const { uri, _meta } = request.params;
232
+ const response = await this.makeRequest('resources/read', {
233
+ uri,
234
+ ...(_meta && { _meta }),
235
+ ...this.getParams(),
236
+ });
237
+ if (isFatalError(response)) {
238
+ throw new Error(response.error_message);
239
+ }
240
+ return response;
241
+ }
242
+ setupHandlers() {
243
+ if (!__classPrivateFieldGet(this, _MCPWebClient_server, "f"))
244
+ return;
245
+ // Handle tool listing
246
+ __classPrivateFieldGet(this, _MCPWebClient_server, "f").setRequestHandler(ListToolsRequestSchema, () => this.makeListToolsRequest());
247
+ // Handle tool calls
248
+ __classPrivateFieldGet(this, _MCPWebClient_server, "f").setRequestHandler(CallToolRequestSchema, this.makeToolCallRequest.bind(this));
249
+ // Handle resource listing
250
+ __classPrivateFieldGet(this, _MCPWebClient_server, "f").setRequestHandler(ListResourcesRequestSchema, () => this.makeListResourcesRequest());
251
+ // Handle resource reading
252
+ __classPrivateFieldGet(this, _MCPWebClient_server, "f").setRequestHandler(ReadResourceRequestSchema, (request) => this.makeReadResourceRequest(request));
253
+ // Handle prompt listing
254
+ __classPrivateFieldGet(this, _MCPWebClient_server, "f").setRequestHandler(ListPromptsRequestSchema, () => this.makeListPromptsRequest());
255
+ }
256
+ /**
257
+ * Creates a contextualized client for a specific query.
258
+ *
259
+ * All tool calls made through the returned client will be tagged with the
260
+ * query UUID, enabling the bridge to track tool calls for that query.
261
+ *
262
+ * @param query - The query object containing uuid and optional responseTool
263
+ * @returns A new MCPWebClient instance bound to the query context
264
+ *
265
+ * @example
266
+ * ```typescript
267
+ * const contextualClient = client.contextualize(query);
268
+ * await contextualClient.callTool('analyze_data');
269
+ * await contextualClient.complete('Analysis complete');
270
+ * ```
271
+ */
272
+ contextualize(query) {
273
+ return new MCPWebClient(__classPrivateFieldGet(this, _MCPWebClient_config, "f"), query);
274
+ }
275
+ /**
276
+ * Calls a tool on the connected frontend.
277
+ *
278
+ * Automatically includes query context if this is a contextualized client.
279
+ * If the query has tool restrictions, only allowed tools can be called.
280
+ *
281
+ * @param name - Name of the tool to call
282
+ * @param args - Optional arguments to pass to the tool
283
+ * @param sessionId - Optional session ID for multi-session scenarios
284
+ * @returns Tool execution result
285
+ * @throws {Error} If query is already done or tool is not allowed
286
+ *
287
+ * @example
288
+ * ```typescript
289
+ * const result = await client.callTool('create_todo', {
290
+ * title: 'New task',
291
+ * priority: 'high',
292
+ * });
293
+ * ```
294
+ */
295
+ async callTool(name, args, sessionId) {
296
+ if (__classPrivateFieldGet(this, _MCPWebClient_query, "f") && __classPrivateFieldGet(this, _MCPWebClient_isDone, "f")) {
297
+ throw new Error(QueryDoneErrorCode);
298
+ }
299
+ // Check tool restrictions if query has them
300
+ if (__classPrivateFieldGet(this, _MCPWebClient_query, "f")?.restrictTools && __classPrivateFieldGet(this, _MCPWebClient_query, "f")?.tools) {
301
+ const allowed = __classPrivateFieldGet(this, _MCPWebClient_query, "f").tools.some(t => t.name === name);
302
+ if (!allowed) {
303
+ throw new Error(`Tool '${name}' not allowed. Query restricted to: ${__classPrivateFieldGet(this, _MCPWebClient_query, "f").tools.map(t => t.name).join(', ')}`);
304
+ }
305
+ }
306
+ const request = {
307
+ method: 'tools/call',
308
+ params: {
309
+ name,
310
+ arguments: args || {},
311
+ // Augment with query context if this is a contextualized instance
312
+ ...this.getParams(sessionId)
313
+ },
314
+ };
315
+ const response = await this.makeToolCallRequest(request);
316
+ // Auto-complete if this was the responseTool and it succeeded
317
+ // Note: response.isError is true for errors, undefined for success
318
+ if (__classPrivateFieldGet(this, _MCPWebClient_query, "f")?.responseTool?.name === name && response.isError !== true) {
319
+ __classPrivateFieldSet(this, _MCPWebClient_isDone, true, "f");
320
+ }
321
+ return response;
322
+ }
323
+ /**
324
+ * Lists all available tools from the connected frontend.
325
+ *
326
+ * If this is a contextualized client with restricted tools, returns only
327
+ * those tools. Otherwise fetches all tools from the bridge.
328
+ *
329
+ * @param sessionId - Optional session ID for multi-session scenarios
330
+ * @returns List of available tools
331
+ * @throws {Error} If query is already done
332
+ */
333
+ async listTools(sessionId) {
334
+ if (__classPrivateFieldGet(this, _MCPWebClient_isDone, "f")) {
335
+ throw new Error(QueryDoneErrorCode);
336
+ }
337
+ // If we have tools from the query, return those
338
+ if (__classPrivateFieldGet(this, _MCPWebClient_query, "f")?.tools) {
339
+ // Need to convert ToolDefinition to Tool format expected by MCP
340
+ const tools = __classPrivateFieldGet(this, _MCPWebClient_query, "f").tools.map(t => ({
341
+ name: t.name,
342
+ description: t.description,
343
+ inputSchema: t.inputSchema || { type: 'object', properties: {}, required: [] }
344
+ }));
345
+ return { tools: tools };
346
+ }
347
+ // Otherwise use the shared request handler
348
+ return this.makeListToolsRequest(sessionId);
349
+ }
350
+ /**
351
+ * Lists all available resources from the connected frontend.
352
+ *
353
+ * @param sessionId - Optional session ID for multi-session scenarios
354
+ * @returns List of available resources
355
+ * @throws {Error} If query is already done
356
+ */
357
+ async listResources(sessionId) {
358
+ if (__classPrivateFieldGet(this, _MCPWebClient_isDone, "f")) {
359
+ throw new Error(QueryDoneErrorCode);
360
+ }
361
+ return this.makeListResourcesRequest(sessionId);
362
+ }
363
+ /**
364
+ * Lists all available prompts from the connected frontend.
365
+ *
366
+ * @param sessionId - Optional session ID for multi-session scenarios
367
+ * @returns List of available prompts
368
+ * @throws {Error} If query is already done
369
+ */
370
+ async listPrompts(sessionId) {
371
+ if (__classPrivateFieldGet(this, _MCPWebClient_isDone, "f")) {
372
+ throw new Error(QueryDoneErrorCode);
373
+ }
374
+ return this.makeListPromptsRequest(sessionId);
375
+ }
376
+ /**
377
+ * Sends a progress update for the current query.
378
+ *
379
+ * Use this to provide intermediate updates during long-running operations.
380
+ * Can only be called on a contextualized client instance.
381
+ *
382
+ * @param message - Progress message to send to the frontend
383
+ * @throws {Error} If not a contextualized client or query is done
384
+ *
385
+ * @example
386
+ * ```typescript
387
+ * await contextualClient.sendProgress('Processing step 1 of 3...');
388
+ * // ... do work ...
389
+ * await contextualClient.sendProgress('Processing step 2 of 3...');
390
+ * ```
391
+ */
392
+ async sendProgress(message) {
393
+ if (!__classPrivateFieldGet(this, _MCPWebClient_query, "f")) {
394
+ throw new Error(ClientNotConextualizedErrorCode);
395
+ }
396
+ if (__classPrivateFieldGet(this, _MCPWebClient_isDone, "f")) {
397
+ throw new Error(QueryDoneErrorCode);
398
+ }
399
+ const url = __classPrivateFieldGet(this, _MCPWebClient_config, "f").serverUrl.replace('ws:', 'http:').replace('wss:', 'https:');
400
+ const progressUrl = `${url}/query/${__classPrivateFieldGet(this, _MCPWebClient_query, "f").uuid}/progress`;
401
+ const response = await fetch(progressUrl, {
402
+ method: 'POST',
403
+ headers: {
404
+ 'Content-Type': 'application/json',
405
+ },
406
+ body: JSON.stringify({ message })
407
+ });
408
+ if (!response.ok) {
409
+ const errorData = await response.json().catch(() => ({ error: response.statusText }));
410
+ throw new Error(errorData.error || `Failed to send progress: HTTP ${response.status}`);
411
+ }
412
+ }
413
+ /**
414
+ * Marks the current query as complete with a message.
415
+ *
416
+ * Can only be called on a contextualized client instance.
417
+ * If the query specified a responseTool, call that tool instead - calling
418
+ * this method will result in an error.
419
+ *
420
+ * @param message - Completion message to send to the frontend
421
+ * @throws {Error} If not a contextualized client, query is done, or responseTool was specified
422
+ *
423
+ * @example
424
+ * ```typescript
425
+ * await contextualClient.complete('Analysis complete: found 5 issues');
426
+ * ```
427
+ */
428
+ async complete(message) {
429
+ if (!__classPrivateFieldGet(this, _MCPWebClient_query, "f")) {
430
+ throw new Error(ClientNotConextualizedErrorCode);
431
+ }
432
+ if (__classPrivateFieldGet(this, _MCPWebClient_isDone, "f")) {
433
+ throw new Error(QueryDoneErrorCode);
434
+ }
435
+ const url = __classPrivateFieldGet(this, _MCPWebClient_config, "f").serverUrl.replace('ws:', 'http:').replace('wss:', 'https:');
436
+ const completeUrl = `${url}/query/${__classPrivateFieldGet(this, _MCPWebClient_query, "f").uuid}/complete`;
437
+ try {
438
+ const response = await fetch(completeUrl, {
439
+ method: 'PUT',
440
+ headers: {
441
+ 'Content-Type': 'application/json',
442
+ },
443
+ body: JSON.stringify({ message })
444
+ });
445
+ if (!response.ok) {
446
+ const errorData = await response.json().catch(() => ({ error: response.statusText }));
447
+ throw new Error(`Failed to complete query: ${errorData.error || response.statusText}`);
448
+ }
449
+ // Only mark as completed after successful response
450
+ __classPrivateFieldSet(this, _MCPWebClient_isDone, true, "f");
451
+ }
452
+ catch (error) {
453
+ throw error;
454
+ }
455
+ }
456
+ /**
457
+ * Marks the current query as failed with an error message.
458
+ *
459
+ * Can only be called on a contextualized client instance.
460
+ * Use this when the query encounters an unrecoverable error.
461
+ *
462
+ * @param error - Error message or Error object describing the failure
463
+ * @throws {Error} If not a contextualized client or query is already done
464
+ *
465
+ * @example
466
+ * ```typescript
467
+ * try {
468
+ * await contextualClient.callTool('risky_operation');
469
+ * } catch (e) {
470
+ * await contextualClient.fail(e);
471
+ * }
472
+ * ```
473
+ */
474
+ async fail(error) {
475
+ if (!__classPrivateFieldGet(this, _MCPWebClient_query, "f")) {
476
+ throw new Error(ClientNotConextualizedErrorCode);
477
+ }
478
+ if (__classPrivateFieldGet(this, _MCPWebClient_isDone, "f")) {
479
+ throw new Error(QueryDoneErrorCode);
480
+ }
481
+ const errorMessage = typeof error === 'string' ? error : error.message;
482
+ const url = __classPrivateFieldGet(this, _MCPWebClient_config, "f").serverUrl.replace('ws:', 'http:').replace('wss:', 'https:');
483
+ const failUrl = `${url}/query/${__classPrivateFieldGet(this, _MCPWebClient_query, "f").uuid}/fail`;
484
+ try {
485
+ const response = await fetch(failUrl, {
486
+ method: 'PUT',
487
+ headers: {
488
+ 'Content-Type': 'application/json',
489
+ },
490
+ body: JSON.stringify({ error: errorMessage })
491
+ });
492
+ if (!response.ok) {
493
+ const errorData = await response.json().catch(() => ({ error: response.statusText }));
494
+ throw new Error(`Failed to mark query as failed: ${errorData.error || response.statusText}`);
495
+ }
496
+ // Mark as completed to prevent further operations
497
+ __classPrivateFieldSet(this, _MCPWebClient_isDone, true, "f");
498
+ }
499
+ catch (err) {
500
+ throw err;
501
+ }
502
+ }
503
+ /**
504
+ * Cancels the current query.
505
+ *
506
+ * Can only be called on a contextualized client instance.
507
+ * Use this when the user or system needs to abort query processing.
508
+ *
509
+ * @param reason - Optional reason for the cancellation
510
+ * @throws {Error} If not a contextualized client or query is already done
511
+ *
512
+ * @example
513
+ * ```typescript
514
+ * // User requested cancellation
515
+ * await contextualClient.cancel('User cancelled operation');
516
+ * ```
517
+ */
518
+ async cancel(reason) {
519
+ if (!__classPrivateFieldGet(this, _MCPWebClient_query, "f")) {
520
+ throw new Error(ClientNotConextualizedErrorCode);
521
+ }
522
+ if (__classPrivateFieldGet(this, _MCPWebClient_isDone, "f")) {
523
+ throw new Error(QueryDoneErrorCode);
524
+ }
525
+ const url = __classPrivateFieldGet(this, _MCPWebClient_config, "f").serverUrl.replace('ws:', 'http:').replace('wss:', 'https:');
526
+ const cancelUrl = `${url}/query/${__classPrivateFieldGet(this, _MCPWebClient_query, "f").uuid}/cancel`;
527
+ try {
528
+ const response = await fetch(cancelUrl, {
529
+ method: 'PUT',
530
+ headers: {
531
+ 'Content-Type': 'application/json',
532
+ },
533
+ body: JSON.stringify(reason ? { reason } : {})
534
+ });
535
+ if (!response.ok) {
536
+ const errorData = await response.json().catch(() => ({ error: response.statusText }));
537
+ throw new Error(`Failed to cancel query: ${errorData.error || response.statusText}`);
538
+ }
539
+ // Mark as completed to prevent further operations
540
+ __classPrivateFieldSet(this, _MCPWebClient_isDone, true, "f");
541
+ }
542
+ catch (err) {
543
+ throw err;
544
+ }
545
+ }
546
+ async makeRequest(method, params) {
547
+ const url = __classPrivateFieldGet(this, _MCPWebClient_config, "f").serverUrl.replace('ws:', 'http:').replace('wss:', 'https:');
548
+ const requestBody = JsonRpcRequestSchema.parse({
549
+ jsonrpc: '2.0',
550
+ id: Date.now(),
551
+ method,
552
+ params
553
+ });
554
+ try {
555
+ const controller = new AbortController();
556
+ const timeoutId = setTimeout(() => controller.abort(), __classPrivateFieldGet(this, _MCPWebClient_config, "f").timeout);
557
+ // Only include Authorization header if we have an authToken
558
+ const headers = {
559
+ 'Content-Type': 'application/json',
560
+ };
561
+ if (__classPrivateFieldGet(this, _MCPWebClient_config, "f").authToken) {
562
+ headers.Authorization = `Bearer ${__classPrivateFieldGet(this, _MCPWebClient_config, "f").authToken}`;
563
+ }
564
+ const response = await fetch(url, {
565
+ method: 'POST',
566
+ headers,
567
+ body: JSON.stringify(requestBody),
568
+ signal: controller.signal
569
+ });
570
+ clearTimeout(timeoutId);
571
+ if (!response.ok) {
572
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
573
+ }
574
+ const rawData = await response.json();
575
+ const data = JsonRpcResponseSchema.parse(rawData);
576
+ if (data.error) {
577
+ throw new Error(data.error.message);
578
+ }
579
+ return data.result;
580
+ }
581
+ catch (error) {
582
+ if (error instanceof Error) {
583
+ if (error.name === 'AbortError') {
584
+ throw new Error('Request timeout');
585
+ }
586
+ throw error;
587
+ }
588
+ throw new Error(`Unknown error: ${error}`);
589
+ }
590
+ }
591
+ /**
592
+ * Fetches server identity (name, version, icon) from the bridge.
593
+ * Falls back to defaults if the bridge is unreachable.
594
+ */
595
+ async fetchBridgeInfo() {
596
+ const defaults = { name: '@mcp-web/client', version: '1.0.0' };
597
+ try {
598
+ const url = __classPrivateFieldGet(this, _MCPWebClient_config, "f").serverUrl
599
+ .replace('ws:', 'http:')
600
+ .replace('wss:', 'https:');
601
+ const controller = new AbortController();
602
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
603
+ const response = await fetch(url, {
604
+ method: 'GET',
605
+ signal: controller.signal,
606
+ });
607
+ clearTimeout(timeoutId);
608
+ if (!response.ok)
609
+ return defaults;
610
+ const data = (await response.json());
611
+ return {
612
+ name: typeof data.name === 'string' ? data.name : defaults.name,
613
+ version: typeof data.version === 'string'
614
+ ? data.version
615
+ : defaults.version,
616
+ ...(typeof data.icon === 'string' && { icon: data.icon }),
617
+ };
618
+ }
619
+ catch {
620
+ return defaults;
621
+ }
622
+ }
623
+ /**
624
+ * Starts the MCP server using stdio transport.
625
+ *
626
+ * This method is intended for running as a subprocess of an AI host like
627
+ * Claude Desktop. It connects to stdin/stdout for MCP communication.
628
+ *
629
+ * Cannot be called on contextualized client instances.
630
+ *
631
+ * @throws {Error} If called on a contextualized client or server not initialized
632
+ *
633
+ * @example
634
+ * ```typescript
635
+ * // In your entry point script
636
+ * const client = new MCPWebClient(config);
637
+ * await client.run();
638
+ * ```
639
+ */
640
+ async run() {
641
+ if (__classPrivateFieldGet(this, _MCPWebClient_query, "f")) {
642
+ throw new Error('Cannot run a contextualized client instance. Only root clients can be run as MCP servers.');
643
+ }
644
+ // Fetch bridge identity before creating the MCP server
645
+ const bridgeInfo = await this.fetchBridgeInfo();
646
+ __classPrivateFieldSet(this, _MCPWebClient_server, new Server({
647
+ name: bridgeInfo.name,
648
+ version: bridgeInfo.version,
649
+ ...(bridgeInfo.icon && { icon: bridgeInfo.icon }),
650
+ }, {
651
+ capabilities: {
652
+ tools: {},
653
+ resources: {},
654
+ prompts: {},
655
+ },
656
+ }), "f");
657
+ this.setupHandlers();
658
+ const transport = new StdioServerTransport();
659
+ await __classPrivateFieldGet(this, _MCPWebClient_server, "f").connect(transport);
660
+ }
661
+ }
662
+ _MCPWebClient_config = new WeakMap(), _MCPWebClient_server = new WeakMap(), _MCPWebClient_query = new WeakMap(), _MCPWebClient_isDone = new WeakMap();
663
+ //# sourceMappingURL=client.js.map