@mcp-web/client 0.1.0 → 0.1.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAKA,YAAY,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,mBAAmB,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAOA,YAAY,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,mBAAmB,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,16 +1,19 @@
1
1
  #!/usr/bin/env node
2
+ import { realpathSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
2
4
  import { MCPWebClient } from './client.js';
3
5
  import { MCPWebClientConfigSchema } from './schemas.js';
4
6
  // Export for programmatic use
5
7
  export { MCPWebClient } from './client.js';
6
8
  // Only run as CLI if this is the main module in Node.js
7
9
  // Guard against running in Deno or when bundled
10
+ // Uses realpathSync on both sides to handle symlinks (e.g. npx, pnpm)
8
11
  // @ts-expect-error - Deno global exists in Deno runtime
9
12
  const isDeno = typeof Deno !== 'undefined';
10
13
  const isNodeCLI = !isDeno &&
11
14
  typeof process !== 'undefined' &&
12
- process.argv &&
13
- import.meta.url === `file://${process.argv[1]}`;
15
+ process.argv?.[1] &&
16
+ realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1]);
14
17
  if (isNodeCLI) {
15
18
  // Handle graceful shutdown
16
19
  process.on('SIGINT', () => {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAGxD,8BAA8B;AAC9B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,wDAAwD;AACxD,gDAAgD;AAChD,wDAAwD;AACxD,MAAM,MAAM,GAAG,OAAO,IAAI,KAAK,WAAW,CAAC;AAC3C,MAAM,SAAS,GAAG,CAAC,MAAM;IACvB,OAAO,OAAO,KAAK,WAAW;IAC9B,OAAO,CAAC,IAAI;IACZ,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;AAElD,IAAI,SAAS,EAAE,CAAC;IACd,2BAA2B;IAC3B,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,wBAAwB,CAAC,KAAK,CAAC;QAC5C,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;QACrC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU;QACjC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpG,CAAC,CAAC;IAEH,mBAAmB;IACnB,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QAC3B,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;QAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAGxD,8BAA8B;AAC9B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,wDAAwD;AACxD,gDAAgD;AAChD,sEAAsE;AACtE,wDAAwD;AACxD,MAAM,MAAM,GAAG,OAAO,IAAI,KAAK,WAAW,CAAC;AAC3C,MAAM,SAAS,GAAG,CAAC,MAAM;IACvB,OAAO,OAAO,KAAK,WAAW;IAC9B,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACjB,YAAY,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAEjF,IAAI,SAAS,EAAE,CAAC;IACd,2BAA2B;IAC3B,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,wBAAwB,CAAC,KAAK,CAAC;QAC5C,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;QACrC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU;QACjC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpG,CAAC,CAAC;IAEH,mBAAmB;IACnB,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QAC3B,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;QAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- #!/usr/bin/env node
3
2
  var __create = Object.create;
4
3
  var __defProp = Object.defineProperty;
5
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -6790,6 +6789,10 @@ var require_dist = __commonJS({
6790
6789
  }
6791
6790
  });
6792
6791
 
6792
+ // src/index.ts
6793
+ import { realpathSync } from "node:fs";
6794
+ import { fileURLToPath } from "node:url";
6795
+
6793
6796
  // ../../node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/external.js
6794
6797
  var external_exports = {};
6795
6798
  __export(external_exports, {
@@ -19361,8 +19364,8 @@ var McpWebConfigSchema = external_exports.object({
19361
19364
  description: external_exports.string().min(1).describe("The description of the server. This should describe the web app you want the AI App to control."),
19362
19365
  /** The bridge server address as host:port (e.g., 'localhost:3001' or 'bridge.example.com'). Protocol is determined automatically based on page context. */
19363
19366
  bridgeUrl: external_exports.string().optional().default("localhost:3001").transform((url2) => url2.replace(/^(wss?|https?):\/\//, "")).describe("The bridge server address as host:port (e.g., 'localhost:3001' or 'bridge.example.com'). Protocol is determined automatically."),
19364
- /** Either a URL or a data URI like "data:image/png;base64,...". This is shown in the AI App. */
19365
- icon: external_exports.string().optional().describe('Either a URL or a data URI like "data:image/png;base64,...". This is shown in the AI App.'),
19367
+ /** Either a URL or a data URI (e.g., "data:image/svg+xml;base64,..." or "data:image/png;base64,..."). This is shown in the AI App. */
19368
+ icon: external_exports.string().optional().describe('Either a URL or a data URI (e.g., "data:image/svg+xml;base64,..." or "data:image/png;base64,..."). This is shown in the AI App.'),
19366
19369
  /** The agent server address with optional path (e.g., 'localhost:3000' or 'localhost:3000/api/v1/query'). Protocol is determined automatically. Required for query support. */
19367
19370
  agentUrl: external_exports.string().optional().transform((url2) => url2?.replace(/^(wss?|https?):\/\//, "")).describe("The agent server address with optional path (e.g., 'localhost:3000' or 'localhost:3000/api/v1/query'). Protocol is determined automatically."),
19368
19371
  /** Authentication token for the agent. If not provided, will use auto-generated token. */
@@ -27200,7 +27203,7 @@ var MCPWebClient = class _MCPWebClient {
27200
27203
 
27201
27204
  // src/index.ts
27202
27205
  var isDeno = typeof Deno !== "undefined";
27203
- var isNodeCLI = !isDeno && typeof process !== "undefined" && process.argv && import.meta.url === `file://${process.argv[1]}`;
27206
+ var isNodeCLI = !isDeno && typeof process !== "undefined" && process.argv?.[1] && realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1]);
27204
27207
  if (isNodeCLI) {
27205
27208
  process.on("SIGINT", () => {
27206
27209
  console.error("Shutting down MCP Bridge Client...");
package/package.json CHANGED
@@ -1,24 +1,32 @@
1
1
  {
2
2
  "name": "@mcp-web/client",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
- "@mcp-web/client": "./dist/index.js"
7
+ "@mcp-web/client": "./dist/standalone.js"
8
8
  },
9
+ "files": [
10
+ "dist/"
11
+ ],
9
12
  "exports": {
10
13
  ".": "./dist/index.js"
11
14
  },
12
15
  "dependencies": {
13
16
  "@modelcontextprotocol/sdk": "^1.24.0",
14
17
  "zod": "~4.1.12",
15
- "@mcp-web/types": "0.1.0"
18
+ "@mcp-web/types": "0.1.1"
16
19
  },
17
20
  "devDependencies": {
18
21
  "@types/node": "^25.0.9",
19
22
  "esbuild": "^0.24.2",
20
23
  "typescript": "~5.9.3"
21
24
  },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/flekschas/mcp-web",
28
+ "directory": "packages/client"
29
+ },
22
30
  "scripts": {
23
31
  "dev": "tsc --watch",
24
32
  "build": "tsc && node esbuild.standalone.mjs",
@@ -1,17 +0,0 @@
1
- import { build } from 'esbuild';
2
-
3
- await build({
4
- entryPoints: ['src/index.ts'],
5
- bundle: true,
6
- platform: 'node',
7
- target: 'node22',
8
- format: 'esm',
9
- outfile: 'dist/standalone.js',
10
- banner: {
11
- js: '#!/usr/bin/env node',
12
- },
13
- minify: false,
14
- sourcemap: false,
15
- });
16
-
17
- console.log('✓ Built standalone bundle: dist/standalone.js');
@@ -1,259 +0,0 @@
1
- import { expect, test } from 'bun:test';
2
- import { ClientNotConextualizedErrorCode, QueryDoneErrorCode } from '@mcp-web/types';
3
- import { MCPWebClient } from './client.js';
4
-
5
- // Store original fetch for tests that need to mock it
6
- const originalFetch = globalThis.fetch;
7
-
8
- test('MCPWebClient initializes with config', () => {
9
- const client = new MCPWebClient({
10
- serverUrl: 'http://localhost:3002',
11
- });
12
-
13
- expect(client).toBeDefined();
14
- });
15
-
16
- test('contextualize creates new client instance with query', () => {
17
- const client = new MCPWebClient({
18
- serverUrl: 'http://localhost:3002',
19
- });
20
-
21
- const query = {
22
- uuid: 'test-query-123',
23
- prompt: 'Test prompt',
24
- context: [],
25
- };
26
-
27
- const contextClient = client.contextualize(query);
28
-
29
- expect(contextClient).toBeDefined();
30
- expect(contextClient !== client).toBe(true);
31
- });
32
-
33
- test('callTool validates restrictTools', async () => {
34
- const client = new MCPWebClient({
35
- serverUrl: 'http://localhost:3002',
36
- });
37
-
38
- const query = {
39
- uuid: 'test-query',
40
- prompt: 'test',
41
- context: [],
42
- tools: [
43
- {
44
- name: 'allowed-tool',
45
- description: 'Allowed tool',
46
- handler: () => {},
47
- },
48
- ],
49
- restrictTools: true,
50
- };
51
-
52
- const contextClient = client.contextualize(query);
53
-
54
- // Should throw when calling a tool not in the allowed list
55
- expect(async () => {
56
- await contextClient.callTool('forbidden-tool', {});
57
- }).toThrow('not allowed');
58
- });
59
-
60
- test('callTool allows tools when restrictTools is false', async () => {
61
- // Mock fetch to return success - Deno has native fetch!
62
- globalThis.fetch = (async () => {
63
- return new Response(
64
- JSON.stringify({
65
- jsonrpc: '2.0',
66
- id: 1,
67
- result: { data: 'success' },
68
- }),
69
- { status: 200, headers: { 'Content-Type': 'application/json' } }
70
- );
71
- }) as unknown as typeof fetch;
72
-
73
- const client = new MCPWebClient({
74
- serverUrl: 'http://localhost:3002',
75
- });
76
-
77
- const query = {
78
- uuid: 'test-query',
79
- prompt: 'test',
80
- context: [],
81
- tools: [
82
- {
83
- name: 'allowed-tool',
84
- description: 'Allowed tool',
85
- handler: () => {},
86
- },
87
- ],
88
- restrictTools: false, // Not restricting tools
89
- };
90
-
91
- const contextClient = client.contextualize(query);
92
-
93
- // Should succeed even with forbidden tool
94
- const result = await contextClient.callTool('any-tool', {});
95
- expect(result).toBeDefined();
96
-
97
- globalThis.fetch = originalFetch;
98
- });
99
-
100
- test('callTool includes query context in request', async () => {
101
- let capturedBody: unknown;
102
-
103
- // Mock fetch and capture the request body
104
- globalThis.fetch = (async (input: Request | URL | string, init?: RequestInit) => {
105
- // Capture the body from init if it's a string
106
- if (init && typeof init.body === 'string') {
107
- capturedBody = JSON.parse(init.body);
108
- } else if (input instanceof Request) {
109
- // If input is a Request object, clone and read it
110
- capturedBody = await input.clone().json();
111
- }
112
-
113
- return new Response(
114
- JSON.stringify({
115
- jsonrpc: '2.0',
116
- id: 1,
117
- result: { data: 'success' },
118
- }),
119
- { status: 200, headers: { 'Content-Type': 'application/json' } }
120
- );
121
- }) as unknown as typeof fetch;
122
-
123
- const client = new MCPWebClient({
124
- serverUrl: 'http://localhost:3002',
125
- });
126
-
127
- const query = {
128
- uuid: 'test-query-123',
129
- prompt: 'test',
130
- context: [],
131
- };
132
-
133
- const contextClient = client.contextualize(query);
134
- await contextClient.callTool('test_tool', { arg: 'value' });
135
-
136
- // Verify _meta was included in request body
137
- expect(capturedBody).toBeDefined();
138
- const requestBody = capturedBody as Record<string, unknown>;
139
- expect(requestBody.params).toBeDefined();
140
- const params = requestBody.params as Record<string, unknown>;
141
- expect(params._meta).toBeDefined();
142
- const metaParams = params._meta as Record<string, unknown>;
143
- expect(metaParams.queryId).toBe('test-query-123');
144
-
145
- globalThis.fetch = originalFetch;
146
- });
147
-
148
- test('listTools returns query tools when available', async () => {
149
- const client = new MCPWebClient({
150
- serverUrl: 'http://localhost:3002',
151
- });
152
-
153
- const query = {
154
- uuid: 'test-query',
155
- prompt: 'test',
156
- context: [],
157
- tools: [
158
- {
159
- name: 'tool1',
160
- description: 'Tool 1',
161
- handler: () => {},
162
- inputSchema: { type: 'object' as const, properties: {} },
163
- },
164
- {
165
- name: 'tool2',
166
- description: 'Tool 2',
167
- handler: () => {},
168
- },
169
- ],
170
- };
171
-
172
- const contextClient = client.contextualize(query);
173
- const result = await contextClient.listTools();
174
-
175
- expect(result.tools.length).toBe(2);
176
- expect(result.tools[0].name).toBe('tool1');
177
- expect(result.tools[1].name).toBe('tool2');
178
- });
179
-
180
- test('complete marks query as completed and prevents further calls', async () => {
181
- globalThis.fetch = (async () => {
182
- return new Response(JSON.stringify({}), { status: 200 });
183
- }) as unknown as typeof fetch;
184
-
185
- const client = new MCPWebClient({
186
- serverUrl: 'http://localhost:3002',
187
- });
188
-
189
- const query = {
190
- uuid: 'test-query',
191
- prompt: 'test',
192
- context: [],
193
- };
194
-
195
- const contextClient = client.contextualize(query);
196
-
197
- // Complete the query
198
- await contextClient.complete('Done!');
199
-
200
- // Should throw when trying to call tools after completion
201
- expect(async () => {
202
- await contextClient.callTool('test_tool', {});
203
- }).toThrow(QueryDoneErrorCode);
204
-
205
- // Should throw when trying to complete again
206
- expect(async () => {
207
- await contextClient.complete('Done again!');
208
- }).toThrow(QueryDoneErrorCode);
209
-
210
- globalThis.fetch = originalFetch;
211
- });
212
-
213
- test('sendProgress throws on non-contextualized client', async () => {
214
- const client = new MCPWebClient({
215
- serverUrl: 'http://localhost:3002',
216
- });
217
-
218
- expect(async () => {
219
- await client.sendProgress('test');
220
- }).toThrow(ClientNotConextualizedErrorCode);
221
- });
222
-
223
- test('complete throws on non-contextualized client', async () => {
224
- const client = new MCPWebClient({
225
- serverUrl: 'http://localhost:3002',
226
- });
227
-
228
- expect(async () => {
229
- await client.complete('test');
230
- }).toThrow(ClientNotConextualizedErrorCode);
231
- });
232
-
233
- test('fail marks query as failed and prevents further operations', async () => {
234
- globalThis.fetch = (async () => {
235
- return new Response(JSON.stringify({}), { status: 200 });
236
- }) as unknown as typeof fetch;
237
-
238
- const client = new MCPWebClient({
239
- serverUrl: 'http://localhost:3002',
240
- });
241
-
242
- const query = {
243
- uuid: 'test-query',
244
- prompt: 'test',
245
- context: [],
246
- };
247
-
248
- const contextClient = client.contextualize(query);
249
-
250
- // Fail the query
251
- await contextClient.fail('Something went wrong');
252
-
253
- // Should throw when trying to call tools after failure
254
- expect(async () => {
255
- await contextClient.callTool('test_tool', {});
256
- }).toThrow(QueryDoneErrorCode);
257
-
258
- globalThis.fetch = originalFetch;
259
- });
package/src/client.ts DELETED
@@ -1,783 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import {
4
- ClientNotConextualizedErrorCode,
5
- type ErroredListPromptsResult,
6
- type ErroredListResourcesResult,
7
- type ErroredListToolsResult,
8
- type FatalError,
9
- InvalidAuthenticationErrorCode,
10
- type McpRequestMetaParams,
11
- MissingAuthenticationErrorCode,
12
- type Query,
13
- QueryDoneErrorCode,
14
- QueryNotActiveErrorCode,
15
- QueryNotFoundErrorCode,
16
- QuerySchema,
17
- } from '@mcp-web/types';
18
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
19
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
20
- import {
21
- type CallToolRequest,
22
- CallToolRequestSchema,
23
- type CallToolResult,
24
- ListPromptsRequestSchema,
25
- type ListPromptsResult,
26
- ListResourcesRequestSchema,
27
- type ListResourcesResult,
28
- ListToolsRequestSchema,
29
- type ListToolsResult,
30
- type ReadResourceRequest,
31
- ReadResourceRequestSchema,
32
- type ReadResourceResult,
33
- type Tool,
34
- } from '@modelcontextprotocol/sdk/types.js';
35
- import {
36
- JsonRpcRequestSchema,
37
- JsonRpcResponseSchema,
38
- MCPWebClientConfigSchema,
39
- } from './schemas.js';
40
- import type {
41
- Content,
42
- MCPWebClientConfig,
43
- MCPWebClientConfigOutput,
44
- } from './types.js';
45
-
46
- function isFatalError<T extends object>(result: T | FatalError): result is FatalError {
47
- return 'errorIsFatal' in result && result.errorIsFatal === true;
48
- }
49
-
50
- /**
51
- * MCP client that connects AI agents (like Claude Desktop) to the bridge server.
52
- *
53
- * MCPWebClient implements the MCP protocol and can run as a stdio server for
54
- * AI host applications, or be used programmatically in agent server code.
55
- *
56
- * @example Running as MCP server for Claude Desktop
57
- * ```typescript
58
- * const client = new MCPWebClient({
59
- * serverUrl: 'http://localhost:3001',
60
- * authToken: 'your-auth-token',
61
- * });
62
- * await client.run(); // Starts stdio transport
63
- * ```
64
- *
65
- * @example Programmatic usage in agent code
66
- * ```typescript
67
- * const client = new MCPWebClient({
68
- * serverUrl: 'http://localhost:3001',
69
- * authToken: 'your-auth-token',
70
- * });
71
- *
72
- * // List available tools
73
- * const { tools } = await client.listTools();
74
- *
75
- * // Call a tool
76
- * const result = await client.callTool('get_todos');
77
- * ```
78
- *
79
- * @example With query context (for agent servers)
80
- * ```typescript
81
- * const contextualClient = client.contextualize(query);
82
- * const result = await contextualClient.callTool('update_todo', { id: '1' });
83
- * await contextualClient.complete('Todo updated successfully');
84
- * ```
85
- */
86
- export class MCPWebClient {
87
- #config: MCPWebClientConfigOutput;
88
- #server?: Server;
89
- #query?: Query;
90
- #isDone = false; // Track if query has been completed
91
-
92
- /**
93
- * Creates a new MCPWebClient instance.
94
- *
95
- * @param config - Client configuration with server URL and auth token
96
- * @param query - Optional query for contextualized instances (internal use)
97
- */
98
- constructor(config: MCPWebClientConfig, query?: Query) {
99
- this.#config = MCPWebClientConfigSchema.parse(config);
100
-
101
- if (query) {
102
- this.#query = QuerySchema.parse(query);
103
- }
104
- }
105
-
106
- private getMetaParams(sessionId?: string): McpRequestMetaParams | undefined {
107
- if (sessionId || this.#query?.uuid) {
108
- const meta: McpRequestMetaParams = {};
109
- if (sessionId) {
110
- meta.sessionId = sessionId;
111
- }
112
- if (this.#query?.uuid) {
113
- meta.queryId = this.#query.uuid;
114
- }
115
- return meta;
116
- }
117
- return undefined;
118
- }
119
-
120
- private getParams(sessionId?: string): { _meta: McpRequestMetaParams } | undefined {
121
- const meta = this.getMetaParams(sessionId);
122
- if (meta) {
123
- return { _meta: meta };
124
- }
125
- return undefined;
126
- }
127
-
128
- private async makeToolCallRequest(request: CallToolRequest): Promise<CallToolResult> {
129
- try {
130
- const { name, arguments: args, _meta } = request.params as any;
131
-
132
- const response = await this.makeRequest('tools/call', {
133
- name,
134
- arguments: args || {},
135
- ...(_meta && { _meta })
136
- });
137
-
138
- // Check if response is already in CallToolResult format (from bridge)
139
- // This happens when the bridge wraps results for Remote MCP compatibility
140
- if (
141
- response &&
142
- typeof response === 'object' &&
143
- 'content' in response &&
144
- Array.isArray((response as { content: unknown }).content)
145
- ) {
146
- return response as CallToolResult;
147
- }
148
-
149
- // Handle different response formats (legacy/unwrapped responses)
150
- // Check if this is an error response from bridge
151
- if (response && typeof response === 'object' && 'error' in response) {
152
- return {
153
- content: [
154
- {
155
- type: 'text',
156
- text: JSON.stringify(response, null, 2),
157
- }
158
- ],
159
- isError: true
160
- };
161
- }
162
-
163
- // Handle successful responses
164
- // The response could be:
165
- // 1. A wrapped response: { data: <actual data> }
166
- // 2. Direct tool result: any type (string, number, object, etc.)
167
- let content: Content[];
168
- let topLevelMeta: Record<string, unknown> | undefined;
169
- let actualData = (response && typeof response === 'object' && 'data' in response)
170
- ? response.data
171
- : response;
172
-
173
- // Extract _meta from the data to place at the top level of CallToolResult.
174
- // The MCP protocol expects _meta as a top-level field on the result object,
175
- // not embedded inside the JSON text content.
176
- if (actualData && typeof actualData === 'object' && '_meta' in actualData) {
177
- const { _meta: extractedMeta, ...rest } = actualData as Record<string, unknown>;
178
- if (extractedMeta && typeof extractedMeta === 'object') {
179
- topLevelMeta = extractedMeta as Record<string, unknown>;
180
- }
181
- actualData = rest;
182
- }
183
-
184
- if (typeof actualData === 'string') {
185
- // Check if it's a data URL (image)
186
- if (actualData.startsWith('data:image/')) {
187
- content = [
188
- {
189
- type: 'image',
190
- data: actualData.split(',')[1],
191
- mimeType: actualData.split(';')[0].split(':')[1],
192
- },
193
- ];
194
- } else {
195
- content = [
196
- {
197
- type: 'text',
198
- text: actualData
199
- }
200
- ];
201
- }
202
- } else if (actualData !== null && actualData !== undefined) {
203
- content = [
204
- {
205
- type: 'text',
206
- text: typeof actualData === 'object' ? JSON.stringify(actualData, null, 2) : String(actualData)
207
- }
208
- ];
209
- } else {
210
- // null or undefined result
211
- content = [
212
- {
213
- type: 'text',
214
- text: ''
215
- }
216
- ];
217
- }
218
-
219
- const callToolResult: CallToolResult = { content };
220
- if (topLevelMeta) {
221
- callToolResult._meta = topLevelMeta;
222
- }
223
- return callToolResult;
224
-
225
- } catch (error) {
226
- // Re-throw authentication and query errors
227
- if (error instanceof Error) {
228
- const errorMessage = error.message;
229
- if (errorMessage === MissingAuthenticationErrorCode ||
230
- errorMessage === InvalidAuthenticationErrorCode ||
231
- errorMessage === QueryNotFoundErrorCode ||
232
- errorMessage === QueryNotActiveErrorCode) {
233
- throw error;
234
- }
235
- }
236
-
237
- // All other errors get returned as CallToolResult with isError: true
238
- return {
239
- content: [
240
- {
241
- type: 'text',
242
- text: `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`
243
- }
244
- ],
245
- isError: true
246
- };
247
- }
248
- }
249
-
250
- private async makeListToolsRequest(sessionId?: string): Promise<ListToolsResult | ErroredListToolsResult> {
251
- const response = await this.makeRequest<ListToolsResult | ErroredListToolsResult | FatalError>('tools/list', this.getParams(sessionId));
252
-
253
- if (isFatalError(response)) {
254
- throw new Error(response.error_message);
255
- }
256
-
257
- return response;
258
- }
259
-
260
- private async makeListResourcesRequest(sessionId?: string): Promise<ListResourcesResult | ErroredListResourcesResult> {
261
- const response = await this.makeRequest<ListResourcesResult | ErroredListResourcesResult | FatalError>('resources/list', this.getParams(sessionId));
262
-
263
- if (isFatalError(response)) {
264
- throw new Error(response.error_message);
265
- }
266
-
267
- return response;
268
- }
269
-
270
- private async makeListPromptsRequest(sessionId?: string): Promise<ListPromptsResult | ErroredListPromptsResult> {
271
- const response = await this.makeRequest<ListPromptsResult | ErroredListPromptsResult | FatalError>('prompts/list', this.getParams(sessionId));
272
-
273
- if (isFatalError(response)) {
274
- throw new Error(response.error_message);
275
- }
276
-
277
- return response;
278
- }
279
-
280
- private async makeReadResourceRequest(request: ReadResourceRequest): Promise<ReadResourceResult> {
281
- const { uri, _meta } = request.params;
282
-
283
- const response = await this.makeRequest<ReadResourceResult | FatalError>('resources/read', {
284
- uri,
285
- ...(_meta && { _meta }),
286
- ...this.getParams(),
287
- });
288
-
289
- if (isFatalError(response)) {
290
- throw new Error(response.error_message);
291
- }
292
-
293
- return response;
294
- }
295
-
296
- private setupHandlers() {
297
- if (!this.#server) return;
298
-
299
- // Handle tool listing
300
- this.#server.setRequestHandler(ListToolsRequestSchema, () => this.makeListToolsRequest());
301
-
302
- // Handle tool calls
303
- this.#server.setRequestHandler(CallToolRequestSchema, this.makeToolCallRequest.bind(this));
304
-
305
- // Handle resource listing
306
- this.#server.setRequestHandler(ListResourcesRequestSchema, () => this.makeListResourcesRequest());
307
-
308
- // Handle resource reading
309
- this.#server.setRequestHandler(ReadResourceRequestSchema, (request: ReadResourceRequest) => this.makeReadResourceRequest(request));
310
-
311
- // Handle prompt listing
312
- this.#server.setRequestHandler(ListPromptsRequestSchema, () => this.makeListPromptsRequest());
313
- }
314
-
315
- /**
316
- * Creates a contextualized client for a specific query.
317
- *
318
- * All tool calls made through the returned client will be tagged with the
319
- * query UUID, enabling the bridge to track tool calls for that query.
320
- *
321
- * @param query - The query object containing uuid and optional responseTool
322
- * @returns A new MCPWebClient instance bound to the query context
323
- *
324
- * @example
325
- * ```typescript
326
- * const contextualClient = client.contextualize(query);
327
- * await contextualClient.callTool('analyze_data');
328
- * await contextualClient.complete('Analysis complete');
329
- * ```
330
- */
331
- contextualize(query: Query): MCPWebClient {
332
- return new MCPWebClient(this.#config, query);
333
- }
334
-
335
- /**
336
- * Calls a tool on the connected frontend.
337
- *
338
- * Automatically includes query context if this is a contextualized client.
339
- * If the query has tool restrictions, only allowed tools can be called.
340
- *
341
- * @param name - Name of the tool to call
342
- * @param args - Optional arguments to pass to the tool
343
- * @param sessionId - Optional session ID for multi-session scenarios
344
- * @returns Tool execution result
345
- * @throws {Error} If query is already done or tool is not allowed
346
- *
347
- * @example
348
- * ```typescript
349
- * const result = await client.callTool('create_todo', {
350
- * title: 'New task',
351
- * priority: 'high',
352
- * });
353
- * ```
354
- */
355
- async callTool(name: string, args?: Record<string, unknown>, sessionId?: string): Promise<CallToolResult> {
356
- if (this.#query && this.#isDone) {
357
- throw new Error(QueryDoneErrorCode);
358
- }
359
-
360
- // Check tool restrictions if query has them
361
- if (this.#query?.restrictTools && this.#query?.tools) {
362
- const allowed = this.#query.tools.some(t => t.name === name);
363
- if (!allowed) {
364
- throw new Error(
365
- `Tool '${name}' not allowed. Query restricted to: ${this.#query.tools.map(t => t.name).join(', ')}`
366
- );
367
- }
368
- }
369
-
370
- const request: CallToolRequest = {
371
- method: 'tools/call',
372
- params: {
373
- name,
374
- arguments: args || {} as Record<string, unknown>,
375
- // Augment with query context if this is a contextualized instance
376
- ...this.getParams(sessionId)
377
- },
378
- };
379
-
380
- const response = await this.makeToolCallRequest(request);
381
-
382
- // Auto-complete if this was the responseTool and it succeeded
383
- // Note: response.isError is true for errors, undefined for success
384
- if (this.#query?.responseTool?.name === name && response.isError !== true) {
385
- this.#isDone = true;
386
- }
387
-
388
- return response;
389
- }
390
-
391
- /**
392
- * Lists all available tools from the connected frontend.
393
- *
394
- * If this is a contextualized client with restricted tools, returns only
395
- * those tools. Otherwise fetches all tools from the bridge.
396
- *
397
- * @param sessionId - Optional session ID for multi-session scenarios
398
- * @returns List of available tools
399
- * @throws {Error} If query is already done
400
- */
401
- async listTools(sessionId?: string): Promise<ListToolsResult | ErroredListToolsResult> {
402
- if (this.#isDone) {
403
- throw new Error(QueryDoneErrorCode);
404
- }
405
-
406
- // If we have tools from the query, return those
407
- if (this.#query?.tools) {
408
- // Need to convert ToolDefinition to Tool format expected by MCP
409
- const tools = this.#query.tools.map(t => ({
410
- name: t.name,
411
- description: t.description,
412
- inputSchema: t.inputSchema || { type: 'object', properties: {}, required: [] }
413
- }));
414
- return { tools: tools as Tool[] };
415
- }
416
-
417
- // Otherwise use the shared request handler
418
- return this.makeListToolsRequest(sessionId);
419
- }
420
-
421
- /**
422
- * Lists all available resources from the connected frontend.
423
- *
424
- * @param sessionId - Optional session ID for multi-session scenarios
425
- * @returns List of available resources
426
- * @throws {Error} If query is already done
427
- */
428
- async listResources(sessionId?: string): Promise<ListResourcesResult | ErroredListResourcesResult> {
429
- if (this.#isDone) {
430
- throw new Error(QueryDoneErrorCode);
431
- }
432
-
433
- return this.makeListResourcesRequest(sessionId);
434
- }
435
-
436
- /**
437
- * Lists all available prompts from the connected frontend.
438
- *
439
- * @param sessionId - Optional session ID for multi-session scenarios
440
- * @returns List of available prompts
441
- * @throws {Error} If query is already done
442
- */
443
- async listPrompts(sessionId?: string): Promise<ListPromptsResult | ErroredListPromptsResult> {
444
- if (this.#isDone) {
445
- throw new Error(QueryDoneErrorCode);
446
- }
447
-
448
- return this.makeListPromptsRequest(sessionId);
449
- }
450
-
451
- /**
452
- * Sends a progress update for the current query.
453
- *
454
- * Use this to provide intermediate updates during long-running operations.
455
- * Can only be called on a contextualized client instance.
456
- *
457
- * @param message - Progress message to send to the frontend
458
- * @throws {Error} If not a contextualized client or query is done
459
- *
460
- * @example
461
- * ```typescript
462
- * await contextualClient.sendProgress('Processing step 1 of 3...');
463
- * // ... do work ...
464
- * await contextualClient.sendProgress('Processing step 2 of 3...');
465
- * ```
466
- */
467
- async sendProgress(message: string): Promise<void> {
468
- if (!this.#query) {
469
- throw new Error(ClientNotConextualizedErrorCode);
470
- }
471
-
472
- if (this.#isDone) {
473
- throw new Error(QueryDoneErrorCode);
474
- }
475
-
476
- const url = this.#config.serverUrl.replace('ws:', 'http:').replace('wss:', 'https:');
477
- const progressUrl = `${url}/query/${this.#query.uuid}/progress`;
478
- const response = await fetch(progressUrl, {
479
- method: 'POST',
480
- headers: {
481
- 'Content-Type': 'application/json',
482
- },
483
- body: JSON.stringify({ message })
484
- });
485
-
486
- if (!response.ok) {
487
- const errorData = await response.json().catch(() => ({ error: response.statusText })) as { error?: string };
488
- throw new Error(errorData.error || `Failed to send progress: HTTP ${response.status}`);
489
- }
490
- }
491
-
492
- /**
493
- * Marks the current query as complete with a message.
494
- *
495
- * Can only be called on a contextualized client instance.
496
- * If the query specified a responseTool, call that tool instead - calling
497
- * this method will result in an error.
498
- *
499
- * @param message - Completion message to send to the frontend
500
- * @throws {Error} If not a contextualized client, query is done, or responseTool was specified
501
- *
502
- * @example
503
- * ```typescript
504
- * await contextualClient.complete('Analysis complete: found 5 issues');
505
- * ```
506
- */
507
- async complete(message: string): Promise<void> {
508
- if (!this.#query) {
509
- throw new Error(ClientNotConextualizedErrorCode);
510
- }
511
-
512
- if (this.#isDone) {
513
- throw new Error(QueryDoneErrorCode);
514
- }
515
-
516
- const url = this.#config.serverUrl.replace('ws:', 'http:').replace('wss:', 'https:');
517
- const completeUrl = `${url}/query/${this.#query.uuid}/complete`;
518
-
519
- try {
520
- const response = await fetch(completeUrl, {
521
- method: 'PUT',
522
- headers: {
523
- 'Content-Type': 'application/json',
524
- },
525
- body: JSON.stringify({ message })
526
- });
527
-
528
- if (!response.ok) {
529
- const errorData = await response.json().catch(() => ({ error: response.statusText })) as { error?: string };
530
- throw new Error(`Failed to complete query: ${errorData.error || response.statusText}`);
531
- }
532
-
533
- // Only mark as completed after successful response
534
- this.#isDone = true;
535
- } catch (error) {
536
- throw error;
537
- }
538
- }
539
-
540
- /**
541
- * Marks the current query as failed with an error message.
542
- *
543
- * Can only be called on a contextualized client instance.
544
- * Use this when the query encounters an unrecoverable error.
545
- *
546
- * @param error - Error message or Error object describing the failure
547
- * @throws {Error} If not a contextualized client or query is already done
548
- *
549
- * @example
550
- * ```typescript
551
- * try {
552
- * await contextualClient.callTool('risky_operation');
553
- * } catch (e) {
554
- * await contextualClient.fail(e);
555
- * }
556
- * ```
557
- */
558
- async fail(error: string | Error): Promise<void> {
559
- if (!this.#query) {
560
- throw new Error(ClientNotConextualizedErrorCode);
561
- }
562
-
563
- if (this.#isDone) {
564
- throw new Error(QueryDoneErrorCode);
565
- }
566
-
567
- const errorMessage = typeof error === 'string' ? error : error.message;
568
- const url = this.#config.serverUrl.replace('ws:', 'http:').replace('wss:', 'https:');
569
- const failUrl = `${url}/query/${this.#query.uuid}/fail`;
570
-
571
- try {
572
- const response = await fetch(failUrl, {
573
- method: 'PUT',
574
- headers: {
575
- 'Content-Type': 'application/json',
576
- },
577
- body: JSON.stringify({ error: errorMessage })
578
- });
579
-
580
- if (!response.ok) {
581
- const errorData = await response.json().catch(() => ({ error: response.statusText })) as { error?: string };
582
- throw new Error(`Failed to mark query as failed: ${errorData.error || response.statusText}`);
583
- }
584
-
585
- // Mark as completed to prevent further operations
586
- this.#isDone = true;
587
- } catch (err) {
588
- throw err;
589
- }
590
- }
591
-
592
- /**
593
- * Cancels the current query.
594
- *
595
- * Can only be called on a contextualized client instance.
596
- * Use this when the user or system needs to abort query processing.
597
- *
598
- * @param reason - Optional reason for the cancellation
599
- * @throws {Error} If not a contextualized client or query is already done
600
- *
601
- * @example
602
- * ```typescript
603
- * // User requested cancellation
604
- * await contextualClient.cancel('User cancelled operation');
605
- * ```
606
- */
607
- async cancel(reason?: string): Promise<void> {
608
- if (!this.#query) {
609
- throw new Error(ClientNotConextualizedErrorCode);
610
- }
611
-
612
- if (this.#isDone) {
613
- throw new Error(QueryDoneErrorCode);
614
- }
615
-
616
- const url = this.#config.serverUrl.replace('ws:', 'http:').replace('wss:', 'https:');
617
- const cancelUrl = `${url}/query/${this.#query.uuid}/cancel`;
618
-
619
- try {
620
- const response = await fetch(cancelUrl, {
621
- method: 'PUT',
622
- headers: {
623
- 'Content-Type': 'application/json',
624
- },
625
- body: JSON.stringify(reason ? { reason } : {})
626
- });
627
-
628
- if (!response.ok) {
629
- const errorData = await response.json().catch(() => ({ error: response.statusText })) as { error?: string };
630
- throw new Error(`Failed to cancel query: ${errorData.error || response.statusText}`);
631
- }
632
-
633
- // Mark as completed to prevent further operations
634
- this.#isDone = true;
635
- } catch (err) {
636
- throw err;
637
- }
638
- }
639
-
640
- private async makeRequest<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T> {
641
- const url = this.#config.serverUrl.replace('ws:', 'http:').replace('wss:', 'https:');
642
-
643
- const requestBody = JsonRpcRequestSchema.parse({
644
- jsonrpc: '2.0',
645
- id: Date.now(),
646
- method,
647
- params
648
- });
649
-
650
- try {
651
- const controller = new AbortController();
652
- const timeoutId = setTimeout(() => controller.abort(), this.#config.timeout);
653
-
654
- // Only include Authorization header if we have an authToken
655
- const headers: Record<string, string> = {
656
- 'Content-Type': 'application/json',
657
- };
658
-
659
- if (this.#config.authToken) {
660
- headers.Authorization = `Bearer ${this.#config.authToken}`;
661
- }
662
-
663
- const response = await fetch(url, {
664
- method: 'POST',
665
- headers,
666
- body: JSON.stringify(requestBody),
667
- signal: controller.signal
668
- });
669
-
670
- clearTimeout(timeoutId);
671
-
672
- if (!response.ok) {
673
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
674
- }
675
-
676
- const rawData = await response.json();
677
-
678
- const data = JsonRpcResponseSchema.parse(rawData);
679
-
680
- if (data.error) {
681
- throw new Error(data.error.message);
682
- }
683
-
684
- return data.result as T;
685
-
686
- } catch (error: unknown) {
687
- if (error instanceof Error) {
688
- if (error.name === 'AbortError') {
689
- throw new Error('Request timeout');
690
- }
691
- throw error;
692
- }
693
- throw new Error(`Unknown error: ${error}`);
694
- }
695
- }
696
-
697
- /**
698
- * Fetches server identity (name, version, icon) from the bridge.
699
- * Falls back to defaults if the bridge is unreachable.
700
- */
701
- private async fetchBridgeInfo(): Promise<{
702
- name: string;
703
- version: string;
704
- icon?: string;
705
- }> {
706
- const defaults = { name: '@mcp-web/client', version: '1.0.0' };
707
- try {
708
- const url = this.#config.serverUrl
709
- .replace('ws:', 'http:')
710
- .replace('wss:', 'https:');
711
-
712
- const controller = new AbortController();
713
- const timeoutId = setTimeout(() => controller.abort(), 5000);
714
-
715
- const response = await fetch(url, {
716
- method: 'GET',
717
- signal: controller.signal,
718
- });
719
-
720
- clearTimeout(timeoutId);
721
-
722
- if (!response.ok) return defaults;
723
-
724
- const data = (await response.json()) as Record<string, unknown>;
725
- return {
726
- name: typeof data.name === 'string' ? data.name : defaults.name,
727
- version:
728
- typeof data.version === 'string'
729
- ? data.version
730
- : defaults.version,
731
- ...(typeof data.icon === 'string' && { icon: data.icon }),
732
- };
733
- } catch {
734
- return defaults;
735
- }
736
- }
737
-
738
- /**
739
- * Starts the MCP server using stdio transport.
740
- *
741
- * This method is intended for running as a subprocess of an AI host like
742
- * Claude Desktop. It connects to stdin/stdout for MCP communication.
743
- *
744
- * Cannot be called on contextualized client instances.
745
- *
746
- * @throws {Error} If called on a contextualized client or server not initialized
747
- *
748
- * @example
749
- * ```typescript
750
- * // In your entry point script
751
- * const client = new MCPWebClient(config);
752
- * await client.run();
753
- * ```
754
- */
755
- async run() {
756
- if (this.#query) {
757
- throw new Error('Cannot run a contextualized client instance. Only root clients can be run as MCP servers.');
758
- }
759
-
760
- // Fetch bridge identity before creating the MCP server
761
- const bridgeInfo = await this.fetchBridgeInfo();
762
-
763
- this.#server = new Server(
764
- {
765
- name: bridgeInfo.name,
766
- version: bridgeInfo.version,
767
- ...(bridgeInfo.icon && { icon: bridgeInfo.icon }),
768
- },
769
- {
770
- capabilities: {
771
- tools: {},
772
- resources: {},
773
- prompts: {},
774
- },
775
- }
776
- );
777
-
778
- this.setupHandlers();
779
-
780
- const transport = new StdioServerTransport();
781
- await this.#server.connect(transport);
782
- }
783
- }
package/src/index.ts DELETED
@@ -1,44 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { MCPWebClient } from './client.js';
4
- import { MCPWebClientConfigSchema } from './schemas.js';
5
-
6
- export type { Query } from '@mcp-web/types';
7
- // Export for programmatic use
8
- export { MCPWebClient } from './client.js';
9
- export type * from './types.js';
10
-
11
- // Only run as CLI if this is the main module in Node.js
12
- // Guard against running in Deno or when bundled
13
- // @ts-expect-error - Deno global exists in Deno runtime
14
- const isDeno = typeof Deno !== 'undefined';
15
- const isNodeCLI = !isDeno &&
16
- typeof process !== 'undefined' &&
17
- process.argv &&
18
- import.meta.url === `file://${process.argv[1]}`;
19
-
20
- if (isNodeCLI) {
21
- // Handle graceful shutdown
22
- process.on('SIGINT', () => {
23
- console.error('Shutting down MCP Bridge Client...');
24
- process.exit(0);
25
- });
26
-
27
- process.on('SIGTERM', () => {
28
- console.error('Shutting down MCP Bridge Client...');
29
- process.exit(0);
30
- });
31
-
32
- const config = MCPWebClientConfigSchema.parse({
33
- serverUrl: process.env.MCP_SERVER_URL,
34
- authToken: process.env.AUTH_TOKEN,
35
- ...(process.env.TIMEOUT !== undefined ? { timeout: Number.parseInt(process.env.TIMEOUT, 10) } : {})
36
- });
37
-
38
- // Start the client
39
- const client = new MCPWebClient(config);
40
- client.run().catch((error) => {
41
- console.error('Failed to start MCP Bridge Client:', error);
42
- process.exit(1);
43
- });
44
- }
package/src/schemas.ts DELETED
@@ -1,38 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- export const JsonRpcResponseSchema = z.object({
4
- jsonrpc: z.literal('2.0').default('2.0'),
5
- id: z.union([z.string(), z.number()]),
6
- result: z.any().optional(),
7
- error: z.object({
8
- code: z.number(),
9
- message: z.string(),
10
- data: z.any().optional()
11
- }).optional()
12
- });
13
-
14
- export const JsonRpcRequestSchema = z.object({
15
- jsonrpc: z.literal('2.0').default('2.0'),
16
- id: z.union([z.string(), z.number()]),
17
- method: z.string(),
18
- params: z.record(z.string(), z.unknown()).optional()
19
- });
20
-
21
- export const MCPWebClientConfigSchema = z.object({
22
- serverUrl: z.url(),
23
- authToken: z.string().min(1).optional(),
24
- timeout: z.number().optional().default(30000)
25
- });
26
-
27
- export const TextContentSchema = z.object({
28
- type: z.literal('text').default('text'),
29
- text: z.string(),
30
- });
31
-
32
- export const ImageContentSchema = z.object({
33
- type: z.literal('image').default('image'),
34
- data: z.string(),
35
- mimeType: z.string(),
36
- });
37
-
38
- export const ContentSchema = z.union([TextContentSchema, ImageContentSchema]);
package/src/types.ts DELETED
@@ -1,14 +0,0 @@
1
- import type { z } from "zod";
2
- import type {
3
- ContentSchema,
4
- ImageContentSchema,
5
- MCPWebClientConfigSchema,
6
- TextContentSchema,
7
- } from "./schemas.js";
8
-
9
- export type MCPWebClientConfig = z.input<typeof MCPWebClientConfigSchema>;
10
- export type MCPWebClientConfigOutput = z.infer<typeof MCPWebClientConfigSchema>;
11
-
12
- export type TextContent = z.infer<typeof TextContentSchema>;
13
- export type ImageContent = z.infer<typeof ImageContentSchema>;
14
- export type Content = z.infer<typeof ContentSchema>;
package/src/utils.ts DELETED
@@ -1,5 +0,0 @@
1
- export const camelToSnakeCase = (str: string) => str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
2
-
3
- export const camelToSnakeCaseProps = (obj: Record<string, unknown>): Record<string, unknown> => {
4
- return Object.fromEntries(Object.entries(obj).map(([key, value]) => [camelToSnakeCase(key), value]))
5
- };
package/tsconfig.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "module": "node16",
5
- "moduleResolution": "node16",
6
- "lib": [
7
- "ES2022"
8
- ],
9
- "outDir": "dist",
10
- "rootDir": "src"
11
- },
12
- "include": [
13
- "src/**/*"
14
- ],
15
- "exclude": [
16
- "node_modules",
17
- "dist",
18
- "**/*.test.ts"
19
- ]
20
- }