@salesforcebob/remote-mcp-bridge 1.0.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.
- package/dist/__tests__/crypto.test.d.ts +2 -0
- package/dist/__tests__/crypto.test.d.ts.map +1 -0
- package/dist/__tests__/crypto.test.js +77 -0
- package/dist/__tests__/crypto.test.js.map +1 -0
- package/dist/__tests__/mcp-server.test.d.ts +2 -0
- package/dist/__tests__/mcp-server.test.d.ts.map +1 -0
- package/dist/__tests__/mcp-server.test.js +157 -0
- package/dist/__tests__/mcp-server.test.js.map +1 -0
- package/dist/crypto.d.ts +13 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +29 -0
- package/dist/crypto.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +74 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server.d.ts +30 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +112 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/tunnel-client.d.ts +32 -0
- package/dist/tunnel-client.d.ts.map +1 -0
- package/dist/tunnel-client.js +84 -0
- package/dist/tunnel-client.js.map +1 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ws-client.d.ts +38 -0
- package/dist/ws-client.d.ts.map +1 -0
- package/dist/ws-client.js +169 -0
- package/dist/ws-client.js.map +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/crypto.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Wallet } from 'ethers';
|
|
3
|
+
import { canonicalize, signPayload } from '../crypto.js';
|
|
4
|
+
describe('canonicalize', () => {
|
|
5
|
+
it('sorts object keys alphabetically', () => {
|
|
6
|
+
const obj = { z: 1, a: 2, m: 3 };
|
|
7
|
+
const result = JSON.parse(canonicalize(obj));
|
|
8
|
+
expect(Object.keys(result)).toEqual(['a', 'm', 'z']);
|
|
9
|
+
});
|
|
10
|
+
it('sorts nested object keys recursively', () => {
|
|
11
|
+
const obj = { b: { z: 1, a: 2 }, a: 1 };
|
|
12
|
+
const result = canonicalize(obj);
|
|
13
|
+
expect(result).toBe('{"a":1,"b":{"a":2,"z":1}}');
|
|
14
|
+
});
|
|
15
|
+
it('does not sort arrays', () => {
|
|
16
|
+
const obj = { items: [3, 1, 2] };
|
|
17
|
+
const result = canonicalize(obj);
|
|
18
|
+
expect(result).toBe('{"items":[3,1,2]}');
|
|
19
|
+
});
|
|
20
|
+
it('produces identical output regardless of key insertion order', () => {
|
|
21
|
+
const a = { method: 'tools/list', id: '123', timestamp: 1000, type: 'request' };
|
|
22
|
+
const b = { id: '123', type: 'request', timestamp: 1000, method: 'tools/list' };
|
|
23
|
+
expect(canonicalize(a)).toBe(canonicalize(b));
|
|
24
|
+
});
|
|
25
|
+
it('handles null and primitive values', () => {
|
|
26
|
+
expect(canonicalize(null)).toBe('null');
|
|
27
|
+
expect(canonicalize(42)).toBe('42');
|
|
28
|
+
expect(canonicalize('hello')).toBe('"hello"');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe('signPayload', () => {
|
|
32
|
+
const TEST_PRIVATE_KEY = '0x5336e184210b834aca58a6f25983490f12ff21a84373703334dd67d543924423';
|
|
33
|
+
const TEST_ADDRESS = '0xAf92293492B0cb01A3A22A91C05C93Fa02a85534';
|
|
34
|
+
it('produces a valid EIP-191 signature', async () => {
|
|
35
|
+
const message = {
|
|
36
|
+
id: 'test-1',
|
|
37
|
+
type: 'request',
|
|
38
|
+
method: 'tools/list',
|
|
39
|
+
timestamp: Date.now(),
|
|
40
|
+
};
|
|
41
|
+
const signature = await signPayload(message, TEST_PRIVATE_KEY);
|
|
42
|
+
expect(signature).toMatch(/^0x[0-9a-f]{130}$/i);
|
|
43
|
+
});
|
|
44
|
+
it('signs with the correct address', async () => {
|
|
45
|
+
const wallet = new Wallet(TEST_PRIVATE_KEY);
|
|
46
|
+
expect(wallet.address).toBe(TEST_ADDRESS);
|
|
47
|
+
});
|
|
48
|
+
it('produces deterministic signatures for the same message', async () => {
|
|
49
|
+
const message = {
|
|
50
|
+
id: 'deterministic-test',
|
|
51
|
+
type: 'request',
|
|
52
|
+
method: 'tools/list',
|
|
53
|
+
timestamp: 1000000,
|
|
54
|
+
};
|
|
55
|
+
const sig1 = await signPayload(message, TEST_PRIVATE_KEY);
|
|
56
|
+
const sig2 = await signPayload(message, TEST_PRIVATE_KEY);
|
|
57
|
+
expect(sig1).toBe(sig2);
|
|
58
|
+
});
|
|
59
|
+
it('produces different signatures for different messages', async () => {
|
|
60
|
+
const msg1 = {
|
|
61
|
+
id: 'msg-1',
|
|
62
|
+
type: 'request',
|
|
63
|
+
method: 'tools/list',
|
|
64
|
+
timestamp: 1000,
|
|
65
|
+
};
|
|
66
|
+
const msg2 = {
|
|
67
|
+
id: 'msg-2',
|
|
68
|
+
type: 'request',
|
|
69
|
+
method: 'tools/call',
|
|
70
|
+
timestamp: 1000,
|
|
71
|
+
};
|
|
72
|
+
const sig1 = await signPayload(msg1, TEST_PRIVATE_KEY);
|
|
73
|
+
const sig2 = await signPayload(msg2, TEST_PRIVATE_KEY);
|
|
74
|
+
expect(sig1).not.toBe(sig2);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
//# sourceMappingURL=crypto.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.test.js","sourceRoot":"","sources":["../../src/__tests__/crypto.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/mcp-server.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* Since jsonSchemaToZod is a private function inside mcp-server.ts,
|
|
5
|
+
* we replicate it here for testing. This ensures the conversion logic
|
|
6
|
+
* works correctly for all JSON Schema types the MCP protocol uses.
|
|
7
|
+
*/
|
|
8
|
+
function jsonSchemaToZod(schema) {
|
|
9
|
+
const type = schema.type;
|
|
10
|
+
if (type === 'object') {
|
|
11
|
+
const properties = (schema.properties || {});
|
|
12
|
+
const required = (schema.required || []);
|
|
13
|
+
const shape = {};
|
|
14
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
15
|
+
let fieldSchema = jsonSchemaToZod(propSchema);
|
|
16
|
+
if (!required.includes(key)) {
|
|
17
|
+
fieldSchema = fieldSchema.optional();
|
|
18
|
+
}
|
|
19
|
+
const description = propSchema.description;
|
|
20
|
+
if (description) {
|
|
21
|
+
fieldSchema = fieldSchema.describe(description);
|
|
22
|
+
}
|
|
23
|
+
shape[key] = fieldSchema;
|
|
24
|
+
}
|
|
25
|
+
return z.object(shape);
|
|
26
|
+
}
|
|
27
|
+
if (type === 'array') {
|
|
28
|
+
const items = (schema.items || {});
|
|
29
|
+
return z.array(jsonSchemaToZod(items));
|
|
30
|
+
}
|
|
31
|
+
if (type === 'string') {
|
|
32
|
+
const enumValues = schema.enum;
|
|
33
|
+
if (enumValues && enumValues.length > 0) {
|
|
34
|
+
return z.enum(enumValues);
|
|
35
|
+
}
|
|
36
|
+
return z.string();
|
|
37
|
+
}
|
|
38
|
+
if (type === 'number' || type === 'integer') {
|
|
39
|
+
return z.number();
|
|
40
|
+
}
|
|
41
|
+
if (type === 'boolean') {
|
|
42
|
+
return z.boolean();
|
|
43
|
+
}
|
|
44
|
+
return z.any();
|
|
45
|
+
}
|
|
46
|
+
describe('jsonSchemaToZod', () => {
|
|
47
|
+
it('converts string type', () => {
|
|
48
|
+
const schema = jsonSchemaToZod({ type: 'string' });
|
|
49
|
+
expect(schema.parse('hello')).toBe('hello');
|
|
50
|
+
expect(() => schema.parse(123)).toThrow();
|
|
51
|
+
});
|
|
52
|
+
it('converts number type', () => {
|
|
53
|
+
const schema = jsonSchemaToZod({ type: 'number' });
|
|
54
|
+
expect(schema.parse(42)).toBe(42);
|
|
55
|
+
expect(() => schema.parse('not a number')).toThrow();
|
|
56
|
+
});
|
|
57
|
+
it('converts integer type as number', () => {
|
|
58
|
+
const schema = jsonSchemaToZod({ type: 'integer' });
|
|
59
|
+
expect(schema.parse(42)).toBe(42);
|
|
60
|
+
});
|
|
61
|
+
it('converts boolean type', () => {
|
|
62
|
+
const schema = jsonSchemaToZod({ type: 'boolean' });
|
|
63
|
+
expect(schema.parse(true)).toBe(true);
|
|
64
|
+
expect(() => schema.parse('true')).toThrow();
|
|
65
|
+
});
|
|
66
|
+
it('converts string enum', () => {
|
|
67
|
+
const schema = jsonSchemaToZod({
|
|
68
|
+
type: 'string',
|
|
69
|
+
enum: ['left', 'right', 'middle'],
|
|
70
|
+
});
|
|
71
|
+
expect(schema.parse('left')).toBe('left');
|
|
72
|
+
expect(() => schema.parse('up')).toThrow();
|
|
73
|
+
});
|
|
74
|
+
it('converts array type', () => {
|
|
75
|
+
const schema = jsonSchemaToZod({
|
|
76
|
+
type: 'array',
|
|
77
|
+
items: { type: 'string' },
|
|
78
|
+
});
|
|
79
|
+
expect(schema.parse(['a', 'b'])).toEqual(['a', 'b']);
|
|
80
|
+
expect(() => schema.parse([1, 2])).toThrow();
|
|
81
|
+
});
|
|
82
|
+
it('converts object with required and optional fields', () => {
|
|
83
|
+
const schema = jsonSchemaToZod({
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
url: { type: 'string', description: 'The URL to navigate to' },
|
|
87
|
+
timeout: { type: 'number' },
|
|
88
|
+
},
|
|
89
|
+
required: ['url'],
|
|
90
|
+
});
|
|
91
|
+
// Required field present
|
|
92
|
+
expect(schema.parse({ url: 'https://example.com' })).toEqual({ url: 'https://example.com' });
|
|
93
|
+
// Optional field included
|
|
94
|
+
expect(schema.parse({ url: 'https://example.com', timeout: 5000 }))
|
|
95
|
+
.toEqual({ url: 'https://example.com', timeout: 5000 });
|
|
96
|
+
// Missing required field
|
|
97
|
+
expect(() => schema.parse({ timeout: 5000 })).toThrow();
|
|
98
|
+
});
|
|
99
|
+
it('converts nested objects', () => {
|
|
100
|
+
const schema = jsonSchemaToZod({
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: {
|
|
103
|
+
name: { type: 'string' },
|
|
104
|
+
options: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: {
|
|
107
|
+
headless: { type: 'boolean' },
|
|
108
|
+
},
|
|
109
|
+
required: ['headless'],
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
required: ['name', 'options'],
|
|
113
|
+
});
|
|
114
|
+
expect(schema.parse({ name: 'test', options: { headless: true } }))
|
|
115
|
+
.toEqual({ name: 'test', options: { headless: true } });
|
|
116
|
+
});
|
|
117
|
+
it('falls back to z.any() for unknown types', () => {
|
|
118
|
+
const schema = jsonSchemaToZod({});
|
|
119
|
+
expect(schema.parse('anything')).toBe('anything');
|
|
120
|
+
expect(schema.parse(42)).toBe(42);
|
|
121
|
+
expect(schema.parse(null)).toBe(null);
|
|
122
|
+
});
|
|
123
|
+
it('handles a real browser_navigate inputSchema', () => {
|
|
124
|
+
const schema = jsonSchemaToZod({
|
|
125
|
+
type: 'object',
|
|
126
|
+
properties: {
|
|
127
|
+
url: {
|
|
128
|
+
type: 'string',
|
|
129
|
+
description: 'The URL to navigate to',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
required: ['url'],
|
|
133
|
+
additionalProperties: false,
|
|
134
|
+
});
|
|
135
|
+
expect(schema.parse({ url: 'https://example.com' })).toEqual({ url: 'https://example.com' });
|
|
136
|
+
});
|
|
137
|
+
it('handles a real browser_click inputSchema', () => {
|
|
138
|
+
const schema = jsonSchemaToZod({
|
|
139
|
+
type: 'object',
|
|
140
|
+
properties: {
|
|
141
|
+
element: { type: 'string', description: 'Human-readable element description' },
|
|
142
|
+
ref: { type: 'string', description: 'Exact target element reference' },
|
|
143
|
+
button: {
|
|
144
|
+
type: 'string',
|
|
145
|
+
enum: ['left', 'right', 'middle'],
|
|
146
|
+
description: 'Button to click',
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
required: ['ref'],
|
|
150
|
+
});
|
|
151
|
+
expect(schema.parse({ ref: 'e123' })).toEqual({ ref: 'e123' });
|
|
152
|
+
expect(schema.parse({ ref: 'e123', button: 'right', element: 'Submit button' }))
|
|
153
|
+
.toEqual({ ref: 'e123', button: 'right', element: 'Submit button' });
|
|
154
|
+
expect(() => schema.parse({ ref: 'e123', button: 'invalid' })).toThrow();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
//# sourceMappingURL=mcp-server.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server.test.js","sourceRoot":"","sources":["../../src/__tests__/mcp-server.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,SAAS,eAAe,CAAC,MAA+B;IACtD,MAAM,IAAI,GAAG,MAAM,CAAC,IAA0B,CAAC;IAE/C,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,MAAM,UAAU,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAA4C,CAAC;QACxF,MAAM,QAAQ,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAa,CAAC;QACrD,MAAM,KAAK,GAAiC,EAAE,CAAC;QAE/C,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3D,IAAI,WAAW,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;YAC9C,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC5B,WAAW,GAAG,WAAW,CAAC,QAAQ,EAAE,CAAC;YACvC,CAAC;YACD,MAAM,WAAW,GAAG,UAAU,CAAC,WAAiC,CAAC;YACjE,IAAI,WAAW,EAAE,CAAC;gBAChB,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;YAClD,CAAC;YACD,KAAK,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC;QAC3B,CAAC;QACD,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC;IAED,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAA4B,CAAC;QAC9D,OAAO,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,MAAM,UAAU,GAAG,MAAM,CAAC,IAA4B,CAAC;QACvD,IAAI,UAAU,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxC,OAAO,CAAC,CAAC,IAAI,CAAC,UAAmC,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;IACpB,CAAC;IAED,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5C,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;IACpB,CAAC;IAED,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC;IACrB,CAAC;IAED,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC;AACjB,CAAC;AAED,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QACpD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QACpD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,MAAM,GAAG,eAAe,CAAC;YAC7B,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC;SAClC,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,MAAM,GAAG,eAAe,CAAC;YAC7B,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;SAC1B,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,MAAM,GAAG,eAAe,CAAC;YAC7B,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,wBAAwB,EAAE;gBAC9D,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;aAC5B;YACD,QAAQ,EAAE,CAAC,KAAK,CAAC;SAClB,CAAC,CAAC;QAEH,yBAAyB;QACzB,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,qBAAqB,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,qBAAqB,EAAE,CAAC,CAAC;QAE7F,0BAA0B;QAC1B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,qBAAqB,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;aAChE,OAAO,CAAC,EAAE,GAAG,EAAE,qBAAqB,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAE1D,yBAAyB;QACzB,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACjC,MAAM,MAAM,GAAG,eAAe,CAAC;YAC7B,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACxB,OAAO,EAAE;oBACP,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;qBAC9B;oBACD,QAAQ,EAAE,CAAC,UAAU,CAAC;iBACvB;aACF;YACD,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC;SAC9B,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;aAChE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAClD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,MAAM,GAAG,eAAe,CAAC;YAC7B,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,GAAG,EAAE;oBACH,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,wBAAwB;iBACtC;aACF;YACD,QAAQ,EAAE,CAAC,KAAK,CAAC;YACjB,oBAAoB,EAAE,KAAK;SAC5B,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,qBAAqB,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,qBAAqB,EAAE,CAAC,CAAC;IAC/F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,MAAM,GAAG,eAAe,CAAC;YAC7B,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,oCAAoC,EAAE;gBAC9E,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gCAAgC,EAAE;gBACtE,MAAM,EAAE;oBACN,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC;oBACjC,WAAW,EAAE,iBAAiB;iBAC/B;aACF;YACD,QAAQ,EAAE,CAAC,KAAK,CAAC;SAClB,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/D,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,CAAC;aAC7E,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,CAAC;QACvE,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/crypto.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BridgeMessage } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic JSON serialization.
|
|
4
|
+
* Sorts object keys recursively to ensure identical strings
|
|
5
|
+
* on both signing and verification sides.
|
|
6
|
+
*/
|
|
7
|
+
export declare function canonicalize(obj: unknown): string;
|
|
8
|
+
/**
|
|
9
|
+
* Sign a BridgeMessage using an Ethereum private key.
|
|
10
|
+
* Returns the EIP-191 signature string.
|
|
11
|
+
*/
|
|
12
|
+
export declare function signPayload(message: BridgeMessage, privateKey: string): Promise<string>;
|
|
13
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAYjD;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,aAAa,EACtB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAIjB"}
|
package/dist/crypto.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Wallet } from 'ethers';
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic JSON serialization.
|
|
4
|
+
* Sorts object keys recursively to ensure identical strings
|
|
5
|
+
* on both signing and verification sides.
|
|
6
|
+
*/
|
|
7
|
+
export function canonicalize(obj) {
|
|
8
|
+
return JSON.stringify(obj, (_key, value) => {
|
|
9
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
10
|
+
return Object.keys(value)
|
|
11
|
+
.sort()
|
|
12
|
+
.reduce((sorted, k) => {
|
|
13
|
+
sorted[k] = value[k];
|
|
14
|
+
return sorted;
|
|
15
|
+
}, {});
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Sign a BridgeMessage using an Ethereum private key.
|
|
22
|
+
* Returns the EIP-191 signature string.
|
|
23
|
+
*/
|
|
24
|
+
export async function signPayload(message, privateKey) {
|
|
25
|
+
const wallet = new Wallet(privateKey);
|
|
26
|
+
const canonical = canonicalize(message);
|
|
27
|
+
return wallet.signMessage(canonical);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACzC,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAChE,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;iBACtB,IAAI,EAAE;iBACN,MAAM,CAA0B,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;gBAC7C,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACrB,OAAO,MAAM,CAAC;YAChB,CAAC,EAAE,EAAE,CAAC,CAAC;QACX,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAsB,EACtB,UAAkB;IAElB,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,UAAU,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACxC,OAAO,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;AACvC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Wallet } from 'ethers';
|
|
3
|
+
import { BridgeWsClient } from './ws-client.js';
|
|
4
|
+
import { RemoteMcpServer } from './mcp-server.js';
|
|
5
|
+
function loadConfig() {
|
|
6
|
+
const privateKey = process.env.PRIVATE_KEY;
|
|
7
|
+
if (!privateKey) {
|
|
8
|
+
throw new Error('PRIVATE_KEY environment variable is required');
|
|
9
|
+
}
|
|
10
|
+
const tunnelUrl = process.env.TUNNEL_URL;
|
|
11
|
+
if (!tunnelUrl) {
|
|
12
|
+
throw new Error('TUNNEL_URL environment variable is required (e.g. "https://mcp-bridge.yourdomain.com")');
|
|
13
|
+
}
|
|
14
|
+
return { privateKey, tunnelUrl };
|
|
15
|
+
}
|
|
16
|
+
async function main() {
|
|
17
|
+
console.error('=== MCP Bridge — Remote Server (stdio) ===\n');
|
|
18
|
+
const config = loadConfig();
|
|
19
|
+
// Derive the public address from the private key for display
|
|
20
|
+
const wallet = new Wallet(config.privateKey);
|
|
21
|
+
console.error(`Signing address: ${wallet.address}`);
|
|
22
|
+
console.error(`Tunnel URL: ${config.tunnelUrl}\n`);
|
|
23
|
+
// 1. Connect to local server via WebSocket through Cloudflare tunnel
|
|
24
|
+
const wsClient = new BridgeWsClient({
|
|
25
|
+
tunnelUrl: config.tunnelUrl,
|
|
26
|
+
privateKey: config.privateKey,
|
|
27
|
+
});
|
|
28
|
+
wsClient.onConnected(() => {
|
|
29
|
+
console.error('[Main] WebSocket connection established via Cloudflare tunnel');
|
|
30
|
+
});
|
|
31
|
+
wsClient.onDisconnected(() => {
|
|
32
|
+
console.error('[Main] WebSocket connection lost — will attempt reconnect');
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
await wsClient.connect();
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
39
|
+
console.error(`[Main] Failed to connect to local server: ${msg}`);
|
|
40
|
+
console.error('\nMake sure:');
|
|
41
|
+
console.error(' 1. The local server is running (pnpm dev:local)');
|
|
42
|
+
console.error(` 2. Tunnel URL (${config.tunnelUrl}) is correct and the tunnel is active`);
|
|
43
|
+
console.error('');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
// 2. Create the MCP server
|
|
47
|
+
const mcpServer = new RemoteMcpServer({ wsClient });
|
|
48
|
+
// 3. Discover tools from the local server and register them
|
|
49
|
+
try {
|
|
50
|
+
await mcpServer.discoverAndRegisterTools();
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
54
|
+
console.error(`[Main] Failed to discover tools: ${msg}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
// 4. Start serving MCP requests via stdio
|
|
58
|
+
await mcpServer.start();
|
|
59
|
+
console.error('\n=== Remote MCP Server is running (stdio) ===\n');
|
|
60
|
+
// Graceful shutdown
|
|
61
|
+
const shutdown = async () => {
|
|
62
|
+
console.error('\nShutting down...');
|
|
63
|
+
await mcpServer.stop();
|
|
64
|
+
wsClient.disconnect();
|
|
65
|
+
process.exit(0);
|
|
66
|
+
};
|
|
67
|
+
process.on('SIGINT', shutdown);
|
|
68
|
+
process.on('SIGTERM', shutdown);
|
|
69
|
+
}
|
|
70
|
+
main().catch((err) => {
|
|
71
|
+
console.error('Fatal error:', err);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
|
74
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { BridgeWsClient } from './ws-client.js';
|
|
2
|
+
export interface McpServerConfig {
|
|
3
|
+
wsClient: BridgeWsClient;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Remote MCP server that exposes browser tools via stdio transport.
|
|
7
|
+
* Forwards all tool calls through the WebSocket bridge to the local server.
|
|
8
|
+
*
|
|
9
|
+
* Uses a single McpServer instance connected to StdioServerTransport.
|
|
10
|
+
*/
|
|
11
|
+
export declare class RemoteMcpServer {
|
|
12
|
+
private config;
|
|
13
|
+
private mcpServer;
|
|
14
|
+
private transport;
|
|
15
|
+
private discoveredTools;
|
|
16
|
+
constructor(config: McpServerConfig);
|
|
17
|
+
/**
|
|
18
|
+
* Discover tools from the local server and register them on the MCP server.
|
|
19
|
+
*/
|
|
20
|
+
discoverAndRegisterTools(): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Start the MCP server with stdio transport.
|
|
23
|
+
*/
|
|
24
|
+
start(): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Shut down the MCP server.
|
|
27
|
+
*/
|
|
28
|
+
stop(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=mcp-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAmDD;;;;;GAKG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,eAAe,CAAc;gBAEzB,MAAM,EAAE,eAAe;IAQnC;;OAEG;IACG,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IA6C/C;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAI5B"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
/**
|
|
5
|
+
* Convert a JSON Schema object (from the tool's inputSchema) to a Zod schema.
|
|
6
|
+
*/
|
|
7
|
+
function jsonSchemaToZod(schema) {
|
|
8
|
+
const type = schema.type;
|
|
9
|
+
if (type === 'object') {
|
|
10
|
+
const properties = (schema.properties || {});
|
|
11
|
+
const required = (schema.required || []);
|
|
12
|
+
const shape = {};
|
|
13
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
14
|
+
let fieldSchema = jsonSchemaToZod(propSchema);
|
|
15
|
+
if (!required.includes(key)) {
|
|
16
|
+
fieldSchema = fieldSchema.optional();
|
|
17
|
+
}
|
|
18
|
+
const description = propSchema.description;
|
|
19
|
+
if (description) {
|
|
20
|
+
fieldSchema = fieldSchema.describe(description);
|
|
21
|
+
}
|
|
22
|
+
shape[key] = fieldSchema;
|
|
23
|
+
}
|
|
24
|
+
return z.object(shape);
|
|
25
|
+
}
|
|
26
|
+
if (type === 'array') {
|
|
27
|
+
const items = (schema.items || {});
|
|
28
|
+
return z.array(jsonSchemaToZod(items));
|
|
29
|
+
}
|
|
30
|
+
if (type === 'string') {
|
|
31
|
+
const enumValues = schema.enum;
|
|
32
|
+
if (enumValues && enumValues.length > 0) {
|
|
33
|
+
return z.enum(enumValues);
|
|
34
|
+
}
|
|
35
|
+
return z.string();
|
|
36
|
+
}
|
|
37
|
+
if (type === 'number' || type === 'integer') {
|
|
38
|
+
return z.number();
|
|
39
|
+
}
|
|
40
|
+
if (type === 'boolean') {
|
|
41
|
+
return z.boolean();
|
|
42
|
+
}
|
|
43
|
+
return z.any();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Remote MCP server that exposes browser tools via stdio transport.
|
|
47
|
+
* Forwards all tool calls through the WebSocket bridge to the local server.
|
|
48
|
+
*
|
|
49
|
+
* Uses a single McpServer instance connected to StdioServerTransport.
|
|
50
|
+
*/
|
|
51
|
+
export class RemoteMcpServer {
|
|
52
|
+
config;
|
|
53
|
+
mcpServer;
|
|
54
|
+
transport = null;
|
|
55
|
+
discoveredTools = [];
|
|
56
|
+
constructor(config) {
|
|
57
|
+
this.config = config;
|
|
58
|
+
this.mcpServer = new McpServer({
|
|
59
|
+
name: 'remote-mcp-bridge',
|
|
60
|
+
version: '1.0.0',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Discover tools from the local server and register them on the MCP server.
|
|
65
|
+
*/
|
|
66
|
+
async discoverAndRegisterTools() {
|
|
67
|
+
console.error('[McpServer] Discovering tools from local server...');
|
|
68
|
+
const response = await this.config.wsClient.sendRequest('tools/list');
|
|
69
|
+
if (response.error) {
|
|
70
|
+
throw new Error(`tools/list failed: ${response.error.message}`);
|
|
71
|
+
}
|
|
72
|
+
const result = response.result;
|
|
73
|
+
this.discoveredTools = result.tools;
|
|
74
|
+
console.error(`[McpServer] Discovered ${this.discoveredTools.length} tools`);
|
|
75
|
+
// Register each tool on the MCP server
|
|
76
|
+
for (const tool of this.discoveredTools) {
|
|
77
|
+
const inputSchema = (tool.inputSchema || { type: 'object', properties: {} });
|
|
78
|
+
const zodSchema = jsonSchemaToZod(inputSchema);
|
|
79
|
+
this.mcpServer.tool(tool.name, tool.description || '', zodSchema instanceof z.ZodObject ? zodSchema.shape : {}, async (args) => {
|
|
80
|
+
console.error(`[McpServer] Proxying tool call: ${tool.name}`);
|
|
81
|
+
const wsResponse = await this.config.wsClient.sendRequest('tools/call', {
|
|
82
|
+
name: tool.name,
|
|
83
|
+
arguments: args,
|
|
84
|
+
});
|
|
85
|
+
if (wsResponse.error) {
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: 'text', text: `Error: ${wsResponse.error.message}` }],
|
|
88
|
+
isError: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return wsResponse.result;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
console.error('[McpServer] All tools registered');
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Start the MCP server with stdio transport.
|
|
98
|
+
*/
|
|
99
|
+
async start() {
|
|
100
|
+
this.transport = new StdioServerTransport();
|
|
101
|
+
await this.mcpServer.connect(this.transport);
|
|
102
|
+
console.error('[McpServer] Connected to stdio transport');
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Shut down the MCP server.
|
|
106
|
+
*/
|
|
107
|
+
async stop() {
|
|
108
|
+
await this.mcpServer.close();
|
|
109
|
+
console.error('[McpServer] Stopped');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=mcp-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface TunnelClientConfig {
|
|
2
|
+
/** Local port to bind the tunnel endpoint on this machine */
|
|
3
|
+
localPort: number;
|
|
4
|
+
/** Remote wstunnel server host (IP or hostname of local machine) */
|
|
5
|
+
tunnelHost: string;
|
|
6
|
+
/** Remote wstunnel server port */
|
|
7
|
+
tunnelPort: number;
|
|
8
|
+
/** Shared secret matching the wstunnel server's path-prefix */
|
|
9
|
+
tunnelSecret: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Manages a wstunnel client subprocess on the remote machine.
|
|
13
|
+
*
|
|
14
|
+
* Spawns: wstunnel client
|
|
15
|
+
* -L tcp://127.0.0.1:<localPort>:localhost:<localPort>
|
|
16
|
+
* --http-upgrade-path-prefix <tunnelSecret>
|
|
17
|
+
* ws://<tunnelHost>:<tunnelPort>
|
|
18
|
+
*
|
|
19
|
+
* After starting, the remote server can connect to ws://127.0.0.1:<localPort>
|
|
20
|
+
* and the traffic is tunneled to the local machine's WebSocket server.
|
|
21
|
+
*/
|
|
22
|
+
export declare class TunnelClient {
|
|
23
|
+
private process;
|
|
24
|
+
private config;
|
|
25
|
+
constructor(config: TunnelClientConfig);
|
|
26
|
+
/**
|
|
27
|
+
* Start the wstunnel client process.
|
|
28
|
+
*/
|
|
29
|
+
start(): Promise<void>;
|
|
30
|
+
stop(): void;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=tunnel-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel-client.d.ts","sourceRoot":"","sources":["../src/tunnel-client.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,UAAU,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,MAAM,CAAqB;gBAEvB,MAAM,EAAE,kBAAkB;IAItC;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgEtB,IAAI,IAAI,IAAI;CAOb"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Manages a wstunnel client subprocess on the remote machine.
|
|
4
|
+
*
|
|
5
|
+
* Spawns: wstunnel client
|
|
6
|
+
* -L tcp://127.0.0.1:<localPort>:localhost:<localPort>
|
|
7
|
+
* --http-upgrade-path-prefix <tunnelSecret>
|
|
8
|
+
* ws://<tunnelHost>:<tunnelPort>
|
|
9
|
+
*
|
|
10
|
+
* After starting, the remote server can connect to ws://127.0.0.1:<localPort>
|
|
11
|
+
* and the traffic is tunneled to the local machine's WebSocket server.
|
|
12
|
+
*/
|
|
13
|
+
export class TunnelClient {
|
|
14
|
+
process = null;
|
|
15
|
+
config;
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.config = config;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Start the wstunnel client process.
|
|
21
|
+
*/
|
|
22
|
+
start() {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const args = [
|
|
25
|
+
'client',
|
|
26
|
+
'-L', `tcp://127.0.0.1:${this.config.localPort}:localhost:${this.config.localPort}`,
|
|
27
|
+
'--http-upgrade-path-prefix', this.config.tunnelSecret,
|
|
28
|
+
`ws://${this.config.tunnelHost}:${this.config.tunnelPort}`,
|
|
29
|
+
];
|
|
30
|
+
console.log(`[TunnelClient] Spawning: wstunnel ${args.join(' ')}`);
|
|
31
|
+
this.process = spawn('wstunnel', args, {
|
|
32
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
33
|
+
});
|
|
34
|
+
let started = false;
|
|
35
|
+
this.process.stderr?.on('data', (data) => {
|
|
36
|
+
const output = data.toString().trim();
|
|
37
|
+
if (output) {
|
|
38
|
+
console.log(`[wstunnel] ${output}`);
|
|
39
|
+
if (!started && (output.includes('listening') || output.includes('Listening') || output.includes('ready') || output.includes('connected'))) {
|
|
40
|
+
started = true;
|
|
41
|
+
resolve();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
this.process.stdout?.on('data', (data) => {
|
|
46
|
+
const output = data.toString().trim();
|
|
47
|
+
if (output) {
|
|
48
|
+
console.log(`[wstunnel stdout] ${output}`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
this.process.on('error', (err) => {
|
|
52
|
+
if (!started) {
|
|
53
|
+
reject(new Error(`Failed to start wstunnel client: ${err.message}. ` +
|
|
54
|
+
'Is wstunnel installed? See: https://github.com/erebe/wstunnel/releases'));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.error(`[TunnelClient] wstunnel error: ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
this.process.on('exit', (code) => {
|
|
61
|
+
console.log(`[TunnelClient] wstunnel exited with code ${code}`);
|
|
62
|
+
if (!started) {
|
|
63
|
+
reject(new Error(`wstunnel client exited with code ${code} before starting`));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
// wstunnel client may not print a clear ready message — assume ready after 3s
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
if (!started) {
|
|
69
|
+
started = true;
|
|
70
|
+
console.log('[TunnelClient] Assuming wstunnel client started');
|
|
71
|
+
resolve();
|
|
72
|
+
}
|
|
73
|
+
}, 3000);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
stop() {
|
|
77
|
+
if (this.process) {
|
|
78
|
+
this.process.kill('SIGTERM');
|
|
79
|
+
this.process = null;
|
|
80
|
+
console.log('[TunnelClient] wstunnel stopped');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=tunnel-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel-client.js","sourceRoot":"","sources":["../src/tunnel-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAa9D;;;;;;;;;;GAUG;AACH,MAAM,OAAO,YAAY;IACf,OAAO,GAAwB,IAAI,CAAC;IACpC,MAAM,CAAqB;IAEnC,YAAY,MAA0B;QACpC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,KAAK;QACH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,IAAI,GAAG;gBACX,QAAQ;gBACR,IAAI,EAAE,mBAAmB,IAAI,CAAC,MAAM,CAAC,SAAS,cAAc,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE;gBACnF,4BAA4B,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY;gBACtD,QAAQ,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE;aAC3D,CAAC;YAEF,OAAO,CAAC,GAAG,CAAC,qCAAqC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAEnE,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE;gBACrC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;aAClC,CAAC,CAAC;YAEH,IAAI,OAAO,GAAG,KAAK,CAAC;YAEpB,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;gBAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;gBACtC,IAAI,MAAM,EAAE,CAAC;oBACX,OAAO,CAAC,GAAG,CAAC,cAAc,MAAM,EAAE,CAAC,CAAC;oBACpC,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;wBAC3I,OAAO,GAAG,IAAI,CAAC;wBACf,OAAO,EAAE,CAAC;oBACZ,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;gBAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;gBACtC,IAAI,MAAM,EAAE,CAAC;oBACX,OAAO,CAAC,GAAG,CAAC,qBAAqB,MAAM,EAAE,CAAC,CAAC;gBAC7C,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,MAAM,CAAC,IAAI,KAAK,CACd,oCAAoC,GAAG,CAAC,OAAO,IAAI;wBACnD,wEAAwE,CACzE,CAAC,CAAC;gBACL,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,KAAK,CAAC,kCAAkC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;gBACjE,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBAC/B,OAAO,CAAC,GAAG,CAAC,4CAA4C,IAAI,EAAE,CAAC,CAAC;gBAChE,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,MAAM,CAAC,IAAI,KAAK,CAAC,oCAAoC,IAAI,kBAAkB,CAAC,CAAC,CAAC;gBAChF,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,8EAA8E;YAC9E,UAAU,CAAC,GAAG,EAAE;gBACd,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,GAAG,IAAI,CAAC;oBACf,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;oBAC/D,OAAO,EAAE,CAAC;gBACZ,CAAC;YACH,CAAC,EAAE,IAAI,CAAC,CAAC;QACX,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC7B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;CACF"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket message envelope for the MCP bridge.
|
|
3
|
+
* Every message between remote and local servers uses this format.
|
|
4
|
+
*/
|
|
5
|
+
export interface BridgeMessage {
|
|
6
|
+
/** Unique ID for request/response correlation */
|
|
7
|
+
id: string;
|
|
8
|
+
/** Message direction */
|
|
9
|
+
type: 'request' | 'response';
|
|
10
|
+
/** MCP method (e.g., 'tools/list', 'tools/call') */
|
|
11
|
+
method: string;
|
|
12
|
+
/** JSON-RPC params (for requests) */
|
|
13
|
+
params?: unknown;
|
|
14
|
+
/** JSON-RPC result (for responses) */
|
|
15
|
+
result?: unknown;
|
|
16
|
+
/** JSON-RPC error (for responses) */
|
|
17
|
+
error?: {
|
|
18
|
+
code: number;
|
|
19
|
+
message: string;
|
|
20
|
+
data?: unknown;
|
|
21
|
+
};
|
|
22
|
+
/** Unix timestamp in ms — for replay protection */
|
|
23
|
+
timestamp: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A BridgeMessage with an attached cryptographic signature.
|
|
27
|
+
* The signature covers all fields except `signature` itself.
|
|
28
|
+
*/
|
|
29
|
+
export interface SignedMessage extends BridgeMessage {
|
|
30
|
+
/** ethers EIP-191 signature of the canonicalized message */
|
|
31
|
+
signature: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Convenience type for responses from local → remote.
|
|
35
|
+
*/
|
|
36
|
+
export interface BridgeResponse extends BridgeMessage {
|
|
37
|
+
type: 'response';
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,iDAAiD;IACjD,EAAE,EAAE,MAAM,CAAC;IACX,wBAAwB;IACxB,IAAI,EAAE,SAAS,GAAG,UAAU,CAAC;IAC7B,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sCAAsC;IACtC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC1D,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAc,SAAQ,aAAa;IAClD,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,IAAI,EAAE,UAAU,CAAC;CAClB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { BridgeResponse } from './types.js';
|
|
2
|
+
export interface WsClientConfig {
|
|
3
|
+
/** Full WSS URL of the Cloudflare tunnel (e.g., wss://xxx.trycloudflare.com) */
|
|
4
|
+
tunnelUrl: string;
|
|
5
|
+
/** Private key for signing messages */
|
|
6
|
+
privateKey: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* WebSocket client that connects to the local server via Cloudflare tunnel.
|
|
10
|
+
* Signs every outgoing message with the ethers private key.
|
|
11
|
+
*/
|
|
12
|
+
export declare class BridgeWsClient {
|
|
13
|
+
private ws;
|
|
14
|
+
private config;
|
|
15
|
+
private pendingRequests;
|
|
16
|
+
private connected;
|
|
17
|
+
private reconnecting;
|
|
18
|
+
private reconnectAttempts;
|
|
19
|
+
private maxReconnectAttempts;
|
|
20
|
+
private onConnectedCallback?;
|
|
21
|
+
private onDisconnectedCallback?;
|
|
22
|
+
constructor(config: WsClientConfig);
|
|
23
|
+
onConnected(callback: () => void): void;
|
|
24
|
+
onDisconnected(callback: () => void): void;
|
|
25
|
+
isConnected(): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Connect to the local server via Cloudflare tunnel WebSocket.
|
|
28
|
+
*/
|
|
29
|
+
connect(): Promise<void>;
|
|
30
|
+
private scheduleReconnect;
|
|
31
|
+
private handleMessage;
|
|
32
|
+
/**
|
|
33
|
+
* Send a signed request through the WebSocket and wait for the response.
|
|
34
|
+
*/
|
|
35
|
+
sendRequest(method: string, params?: unknown, timeoutMs?: number): Promise<BridgeResponse>;
|
|
36
|
+
disconnect(): void;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=ws-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-client.d.ts","sourceRoot":"","sources":["../src/ws-client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAiB,cAAc,EAAiB,MAAM,YAAY,CAAC;AAE/E,MAAM,WAAW,cAAc;IAC7B,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;CACpB;AAQD;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,eAAe,CAAqC;IAC5D,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,oBAAoB,CAAM;IAClC,OAAO,CAAC,mBAAmB,CAAC,CAAa;IACzC,OAAO,CAAC,sBAAsB,CAAC,CAAa;gBAEhC,MAAM,EAAE,cAAc;IAIlC,WAAW,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAIvC,cAAc,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAI1C,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAmExB,OAAO,CAAC,iBAAiB;IAuBzB,OAAO,CAAC,aAAa;IAiBrB;;OAEG;IACG,WAAW,CACf,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,OAAO,EAChB,SAAS,SAAS,GACjB,OAAO,CAAC,cAAc,CAAC;IAoC1B,UAAU,IAAI,IAAI;CAQnB"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import WebSocket from 'ws';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { signPayload } from './crypto.js';
|
|
4
|
+
/**
|
|
5
|
+
* WebSocket client that connects to the local server via Cloudflare tunnel.
|
|
6
|
+
* Signs every outgoing message with the ethers private key.
|
|
7
|
+
*/
|
|
8
|
+
export class BridgeWsClient {
|
|
9
|
+
ws = null;
|
|
10
|
+
config;
|
|
11
|
+
pendingRequests = new Map();
|
|
12
|
+
connected = false;
|
|
13
|
+
reconnecting = false;
|
|
14
|
+
reconnectAttempts = 0;
|
|
15
|
+
maxReconnectAttempts = 10;
|
|
16
|
+
onConnectedCallback;
|
|
17
|
+
onDisconnectedCallback;
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
}
|
|
21
|
+
onConnected(callback) {
|
|
22
|
+
this.onConnectedCallback = callback;
|
|
23
|
+
}
|
|
24
|
+
onDisconnected(callback) {
|
|
25
|
+
this.onDisconnectedCallback = callback;
|
|
26
|
+
}
|
|
27
|
+
isConnected() {
|
|
28
|
+
return this.connected;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Connect to the local server via Cloudflare tunnel WebSocket.
|
|
32
|
+
*/
|
|
33
|
+
connect() {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
// Convert https:// to wss:// if needed
|
|
36
|
+
let wsUrl = this.config.tunnelUrl;
|
|
37
|
+
if (wsUrl.startsWith('https://')) {
|
|
38
|
+
wsUrl = wsUrl.replace(/^https:\/\//, 'wss://');
|
|
39
|
+
}
|
|
40
|
+
else if (!wsUrl.startsWith('wss://') && !wsUrl.startsWith('ws://')) {
|
|
41
|
+
wsUrl = `wss://${wsUrl}`;
|
|
42
|
+
}
|
|
43
|
+
console.error(`[WsClient] Connecting to ${wsUrl}...`);
|
|
44
|
+
this.ws = new WebSocket(wsUrl);
|
|
45
|
+
const connectTimeout = setTimeout(() => {
|
|
46
|
+
if (!this.connected) {
|
|
47
|
+
this.ws?.terminate();
|
|
48
|
+
reject(new Error('Connection timeout after 30s'));
|
|
49
|
+
}
|
|
50
|
+
}, 30_000);
|
|
51
|
+
this.ws.on('open', () => {
|
|
52
|
+
clearTimeout(connectTimeout);
|
|
53
|
+
this.connected = true;
|
|
54
|
+
this.reconnectAttempts = 0;
|
|
55
|
+
console.error('[WsClient] Connected to local server via Cloudflare tunnel');
|
|
56
|
+
this.onConnectedCallback?.();
|
|
57
|
+
resolve();
|
|
58
|
+
});
|
|
59
|
+
this.ws.on('message', (data) => {
|
|
60
|
+
this.handleMessage(data);
|
|
61
|
+
});
|
|
62
|
+
// Respond to server pings to keep the connection alive
|
|
63
|
+
this.ws.on('ping', () => {
|
|
64
|
+
this.ws?.pong();
|
|
65
|
+
});
|
|
66
|
+
this.ws.on('close', (code, reason) => {
|
|
67
|
+
this.connected = false;
|
|
68
|
+
console.error(`[WsClient] Disconnected: ${code} ${reason.toString()}`);
|
|
69
|
+
this.onDisconnectedCallback?.();
|
|
70
|
+
// Reject all pending requests
|
|
71
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
72
|
+
pending.reject(new Error('Connection closed'));
|
|
73
|
+
clearTimeout(pending.timer);
|
|
74
|
+
this.pendingRequests.delete(id);
|
|
75
|
+
}
|
|
76
|
+
// Auto-reconnect
|
|
77
|
+
if (!this.reconnecting) {
|
|
78
|
+
this.scheduleReconnect();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
this.ws.on('error', (err) => {
|
|
82
|
+
clearTimeout(connectTimeout);
|
|
83
|
+
console.error(`[WsClient] Error: ${err.message}`);
|
|
84
|
+
if (!this.connected) {
|
|
85
|
+
reject(err);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
scheduleReconnect() {
|
|
91
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
92
|
+
console.error('[WsClient] Max reconnection attempts reached');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
this.reconnecting = true;
|
|
96
|
+
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30_000);
|
|
97
|
+
this.reconnectAttempts++;
|
|
98
|
+
console.error(`[WsClient] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
|
|
99
|
+
setTimeout(async () => {
|
|
100
|
+
try {
|
|
101
|
+
await this.connect();
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
105
|
+
console.error(`[WsClient] Reconnect failed: ${msg}`);
|
|
106
|
+
}
|
|
107
|
+
this.reconnecting = false;
|
|
108
|
+
}, delay);
|
|
109
|
+
}
|
|
110
|
+
handleMessage(data) {
|
|
111
|
+
try {
|
|
112
|
+
const response = JSON.parse(data.toString('utf-8'));
|
|
113
|
+
const pending = this.pendingRequests.get(response.id);
|
|
114
|
+
if (pending) {
|
|
115
|
+
clearTimeout(pending.timer);
|
|
116
|
+
this.pendingRequests.delete(response.id);
|
|
117
|
+
pending.resolve(response);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.error(`[WsClient] Received response for unknown request: ${response.id}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
console.error('[WsClient] Failed to parse response:', err);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Send a signed request through the WebSocket and wait for the response.
|
|
129
|
+
*/
|
|
130
|
+
async sendRequest(method, params, timeoutMs = 60_000) {
|
|
131
|
+
if (!this.ws || !this.connected) {
|
|
132
|
+
throw new Error('Not connected to local server');
|
|
133
|
+
}
|
|
134
|
+
const id = randomUUID();
|
|
135
|
+
const message = {
|
|
136
|
+
id,
|
|
137
|
+
type: 'request',
|
|
138
|
+
method,
|
|
139
|
+
params,
|
|
140
|
+
timestamp: Date.now(),
|
|
141
|
+
};
|
|
142
|
+
// Sign the payload
|
|
143
|
+
const signature = await signPayload(message, this.config.privateKey);
|
|
144
|
+
const signedMessage = { ...message, signature };
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
const timer = setTimeout(() => {
|
|
147
|
+
this.pendingRequests.delete(id);
|
|
148
|
+
reject(new Error(`Request timed out after ${timeoutMs}ms: ${method}`));
|
|
149
|
+
}, timeoutMs);
|
|
150
|
+
this.pendingRequests.set(id, { resolve, reject, timer });
|
|
151
|
+
this.ws.send(JSON.stringify(signedMessage), (err) => {
|
|
152
|
+
if (err) {
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
this.pendingRequests.delete(id);
|
|
155
|
+
reject(new Error(`Failed to send message: ${err.message}`));
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
disconnect() {
|
|
161
|
+
if (this.ws) {
|
|
162
|
+
this.ws.close();
|
|
163
|
+
this.ws = null;
|
|
164
|
+
this.connected = false;
|
|
165
|
+
console.error('[WsClient] Disconnected');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=ws-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-client.js","sourceRoot":"","sources":["../src/ws-client.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@salesforcebob/remote-mcp-bridge",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Secure MCP bridge for remote browser control via WebSocket + ethers.js signing",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"remote-mcp-bridge": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "node --env-file=.env --import tsx/esm src/index.ts",
|
|
19
|
+
"start": "node dist/index.js",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
24
|
+
"ethers": "^6.13.0",
|
|
25
|
+
"ws": "^8.18.0",
|
|
26
|
+
"zod": "^3.23.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^25.3.1",
|
|
30
|
+
"@types/ws": "^8.5.0",
|
|
31
|
+
"tsx": "^4.19.0",
|
|
32
|
+
"typescript": "^5.5.0"
|
|
33
|
+
},
|
|
34
|
+
"license": "ISC"
|
|
35
|
+
}
|