up-mcp-bridge 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/client.js ADDED
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ Client,
4
+ ListResourcesResultSchema,
5
+ ListToolsResultSchema,
6
+ NodeOAuthClientProvider,
7
+ connectToRemoteServer,
8
+ createLazyAuthCoordinator,
9
+ debugLog,
10
+ fetchAuthorizationServerMetadata,
11
+ log,
12
+ parseCommandLineArgs,
13
+ setupSignalHandlers,
14
+ version
15
+ } from "./chunk-EOYXIWZ7.js";
16
+
17
+ // src/client.ts
18
+ import { EventEmitter } from "events";
19
+ async function runClient(serverUrl, callbackPort, headers, transportStrategy = "http-first", host, staticOAuthClientMetadata, staticOAuthClientInfo, authTimeoutMs, serverUrlHash) {
20
+ const events = new EventEmitter();
21
+ const authCoordinator = createLazyAuthCoordinator(serverUrlHash, callbackPort, events, authTimeoutMs);
22
+ let authorizationServerMetadata;
23
+ try {
24
+ authorizationServerMetadata = await fetchAuthorizationServerMetadata(serverUrl);
25
+ if (authorizationServerMetadata?.scopes_supported) {
26
+ debugLog("Pre-fetched authorization server metadata", {
27
+ scopes_supported: authorizationServerMetadata.scopes_supported
28
+ });
29
+ }
30
+ } catch (error) {
31
+ debugLog("Failed to pre-fetch authorization server metadata", error);
32
+ }
33
+ const authProvider = new NodeOAuthClientProvider({
34
+ serverUrl,
35
+ callbackPort,
36
+ host,
37
+ clientName: "MCP CLI Client",
38
+ staticOAuthClientMetadata,
39
+ staticOAuthClientInfo,
40
+ serverUrlHash,
41
+ authorizationServerMetadata
42
+ });
43
+ const client = new Client(
44
+ {
45
+ name: "mcp-remote",
46
+ version
47
+ },
48
+ {
49
+ capabilities: {}
50
+ }
51
+ );
52
+ let server = null;
53
+ const authInitializer = async () => {
54
+ const authState = await authCoordinator.initializeAuth();
55
+ server = authState.server;
56
+ if (authState.skipBrowserAuth) {
57
+ log("Authentication was completed by another instance - will use tokens from disk...");
58
+ await new Promise((res) => setTimeout(res, 1e3));
59
+ }
60
+ return {
61
+ waitForAuthCode: authState.waitForAuthCode,
62
+ skipBrowserAuth: authState.skipBrowserAuth
63
+ };
64
+ };
65
+ try {
66
+ const transport = await connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy);
67
+ transport.onmessage = (message) => {
68
+ log("Received message:", JSON.stringify(message, null, 2));
69
+ };
70
+ transport.onerror = (error) => {
71
+ log("Transport error:", error);
72
+ };
73
+ transport.onclose = () => {
74
+ log("Connection closed.");
75
+ process.exit(0);
76
+ };
77
+ const cleanup = async () => {
78
+ log("\nClosing connection...");
79
+ await client.close();
80
+ if (server) {
81
+ server.close();
82
+ }
83
+ };
84
+ setupSignalHandlers(cleanup);
85
+ log("Connected successfully!");
86
+ try {
87
+ log("Requesting tools list...");
88
+ const tools = await client.request({ method: "tools/list" }, ListToolsResultSchema);
89
+ log("Tools:", JSON.stringify(tools, null, 2));
90
+ } catch (e) {
91
+ log("Error requesting tools list:", e);
92
+ }
93
+ try {
94
+ log("Requesting resource list...");
95
+ const resources = await client.request({ method: "resources/list" }, ListResourcesResultSchema);
96
+ log("Resources:", JSON.stringify(resources, null, 2));
97
+ } catch (e) {
98
+ log("Error requesting resources list:", e);
99
+ }
100
+ log("Exiting OK...");
101
+ if (server) {
102
+ server.close();
103
+ }
104
+ process.exit(0);
105
+ } catch (error) {
106
+ log("Fatal error:", error);
107
+ if (server) {
108
+ server.close();
109
+ }
110
+ process.exit(1);
111
+ }
112
+ }
113
+ parseCommandLineArgs(process.argv.slice(2), "Usage: npx tsx client.ts <https://server-url> [callback-port] [--debug]").then(
114
+ ({
115
+ serverUrl,
116
+ callbackPort,
117
+ headers,
118
+ transportStrategy,
119
+ host,
120
+ staticOAuthClientMetadata,
121
+ staticOAuthClientInfo,
122
+ authTimeoutMs,
123
+ serverUrlHash
124
+ }) => {
125
+ return runClient(
126
+ serverUrl,
127
+ callbackPort,
128
+ headers,
129
+ transportStrategy,
130
+ host,
131
+ staticOAuthClientMetadata,
132
+ staticOAuthClientInfo,
133
+ authTimeoutMs,
134
+ serverUrlHash
135
+ );
136
+ }
137
+ ).catch((error) => {
138
+ console.error("Fatal error:", error);
139
+ process.exit(1);
140
+ });
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/proxy.js ADDED
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ JSONRPCMessageSchema,
4
+ NodeOAuthClientProvider,
5
+ connectToRemoteServer,
6
+ createLazyAuthCoordinator,
7
+ debugLog,
8
+ fetchAuthorizationServerMetadata,
9
+ log,
10
+ mcpProxy,
11
+ parseCommandLineArgs,
12
+ setupSignalHandlers
13
+ } from "./chunk-EOYXIWZ7.js";
14
+
15
+ // src/proxy.ts
16
+ import { EventEmitter } from "events";
17
+
18
+ // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
19
+ import process2 from "process";
20
+
21
+ // node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
22
+ var ReadBuffer = class {
23
+ append(chunk) {
24
+ this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
25
+ }
26
+ readMessage() {
27
+ if (!this._buffer) {
28
+ return null;
29
+ }
30
+ const index = this._buffer.indexOf("\n");
31
+ if (index === -1) {
32
+ return null;
33
+ }
34
+ const line = this._buffer.toString("utf8", 0, index).replace(/\r$/, "");
35
+ this._buffer = this._buffer.subarray(index + 1);
36
+ return deserializeMessage(line);
37
+ }
38
+ clear() {
39
+ this._buffer = void 0;
40
+ }
41
+ };
42
+ function deserializeMessage(line) {
43
+ return JSONRPCMessageSchema.parse(JSON.parse(line));
44
+ }
45
+ function serializeMessage(message) {
46
+ return JSON.stringify(message) + "\n";
47
+ }
48
+
49
+ // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
50
+ var StdioServerTransport = class {
51
+ constructor(_stdin = process2.stdin, _stdout = process2.stdout) {
52
+ this._stdin = _stdin;
53
+ this._stdout = _stdout;
54
+ this._readBuffer = new ReadBuffer();
55
+ this._started = false;
56
+ this._ondata = (chunk) => {
57
+ this._readBuffer.append(chunk);
58
+ this.processReadBuffer();
59
+ };
60
+ this._onerror = (error) => {
61
+ var _a;
62
+ (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
63
+ };
64
+ }
65
+ /**
66
+ * Starts listening for messages on stdin.
67
+ */
68
+ async start() {
69
+ if (this._started) {
70
+ throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");
71
+ }
72
+ this._started = true;
73
+ this._stdin.on("data", this._ondata);
74
+ this._stdin.on("error", this._onerror);
75
+ }
76
+ processReadBuffer() {
77
+ var _a, _b;
78
+ while (true) {
79
+ try {
80
+ const message = this._readBuffer.readMessage();
81
+ if (message === null) {
82
+ break;
83
+ }
84
+ (_a = this.onmessage) === null || _a === void 0 ? void 0 : _a.call(this, message);
85
+ } catch (error) {
86
+ (_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error);
87
+ }
88
+ }
89
+ }
90
+ async close() {
91
+ var _a;
92
+ this._stdin.off("data", this._ondata);
93
+ this._stdin.off("error", this._onerror);
94
+ const remainingDataListeners = this._stdin.listenerCount("data");
95
+ if (remainingDataListeners === 0) {
96
+ this._stdin.pause();
97
+ }
98
+ this._readBuffer.clear();
99
+ (_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
100
+ }
101
+ send(message) {
102
+ return new Promise((resolve) => {
103
+ const json = serializeMessage(message);
104
+ if (this._stdout.write(json)) {
105
+ resolve();
106
+ } else {
107
+ this._stdout.once("drain", resolve);
108
+ }
109
+ });
110
+ }
111
+ };
112
+
113
+ // src/proxy.ts
114
+ async function runProxy(serverUrl, callbackPort, headers, transportStrategy = "http-first", host, staticOAuthClientMetadata, staticOAuthClientInfo, authorizeResource, ignoredTools, authTimeoutMs, serverUrlHash, reconnectOptions) {
115
+ const events = new EventEmitter();
116
+ const authCoordinator = createLazyAuthCoordinator(serverUrlHash, callbackPort, events, authTimeoutMs);
117
+ let authorizationServerMetadata;
118
+ try {
119
+ authorizationServerMetadata = await fetchAuthorizationServerMetadata(serverUrl);
120
+ if (authorizationServerMetadata?.scopes_supported) {
121
+ debugLog("Pre-fetched authorization server metadata", {
122
+ scopes_supported: authorizationServerMetadata.scopes_supported
123
+ });
124
+ }
125
+ } catch (error) {
126
+ debugLog("Failed to pre-fetch authorization server metadata", error);
127
+ }
128
+ const authProvider = new NodeOAuthClientProvider({
129
+ serverUrl,
130
+ callbackPort,
131
+ host,
132
+ clientName: "MCP CLI Proxy",
133
+ staticOAuthClientMetadata,
134
+ staticOAuthClientInfo,
135
+ authorizeResource,
136
+ serverUrlHash,
137
+ authorizationServerMetadata
138
+ });
139
+ const localTransport = new StdioServerTransport();
140
+ let server = null;
141
+ const authInitializer = async () => {
142
+ const authState = await authCoordinator.initializeAuth();
143
+ server = authState.server;
144
+ if (authState.skipBrowserAuth) {
145
+ log("Authentication was completed by another instance - will use tokens from disk");
146
+ await new Promise((res) => setTimeout(res, 1e3));
147
+ }
148
+ return {
149
+ waitForAuthCode: authState.waitForAuthCode,
150
+ skipBrowserAuth: authState.skipBrowserAuth
151
+ };
152
+ };
153
+ try {
154
+ const remoteTransport = await connectToRemoteServer(null, serverUrl, authProvider, headers, authInitializer, transportStrategy);
155
+ const reconnectFn = async () => {
156
+ log("Creating new connection to remote server...");
157
+ return connectToRemoteServer(null, serverUrl, authProvider, headers, authInitializer, transportStrategy);
158
+ };
159
+ mcpProxy({
160
+ transportToClient: localTransport,
161
+ transportToServer: remoteTransport,
162
+ ignoredTools,
163
+ reconnectFn,
164
+ reconnectOptions,
165
+ serverUrl
166
+ });
167
+ await localTransport.start();
168
+ log("Local STDIO server running");
169
+ log(`Proxy established successfully between local STDIO and remote ${remoteTransport.constructor.name}`);
170
+ if (reconnectOptions.enabled) {
171
+ log(`Auto-reconnect enabled (max ${reconnectOptions.maxAttempts} attempts, base delay ${reconnectOptions.baseDelayMs}ms)`);
172
+ }
173
+ log("Press Ctrl+C to exit");
174
+ const cleanup = async () => {
175
+ await remoteTransport.close();
176
+ await localTransport.close();
177
+ if (server) {
178
+ server.close();
179
+ }
180
+ };
181
+ setupSignalHandlers(cleanup);
182
+ } catch (error) {
183
+ log("Fatal error:", error);
184
+ if (error instanceof Error && error.message.includes("self-signed certificate in certificate chain")) {
185
+ log(`You may be behind a VPN!
186
+
187
+ If you are behind a VPN, you can try setting the NODE_EXTRA_CA_CERTS environment variable to point
188
+ to the CA certificate file. If using claude_desktop_config.json, this might look like:
189
+
190
+ {
191
+ "mcpServers": {
192
+ "\${mcpServerName}": {
193
+ "command": "npx",
194
+ "args": [
195
+ "mcp-remote",
196
+ "https://remote.mcp.server/sse"
197
+ ],
198
+ "env": {
199
+ "NODE_EXTRA_CA_CERTS": "\${your CA certificate file path}.pem"
200
+ }
201
+ }
202
+ }
203
+ }
204
+ `);
205
+ }
206
+ if (server) {
207
+ server.close();
208
+ }
209
+ process.exit(1);
210
+ }
211
+ }
212
+ parseCommandLineArgs(process.argv.slice(2), "Usage: npx tsx proxy.ts <https://server-url> [callback-port] [--debug]").then(
213
+ ({
214
+ serverUrl,
215
+ callbackPort,
216
+ headers,
217
+ transportStrategy,
218
+ host,
219
+ debug,
220
+ staticOAuthClientMetadata,
221
+ staticOAuthClientInfo,
222
+ authorizeResource,
223
+ ignoredTools,
224
+ authTimeoutMs,
225
+ serverUrlHash,
226
+ reconnectOptions
227
+ }) => {
228
+ return runProxy(
229
+ serverUrl,
230
+ callbackPort,
231
+ headers,
232
+ transportStrategy,
233
+ host,
234
+ staticOAuthClientMetadata,
235
+ staticOAuthClientInfo,
236
+ authorizeResource,
237
+ ignoredTools,
238
+ authTimeoutMs,
239
+ serverUrlHash,
240
+ reconnectOptions
241
+ );
242
+ }
243
+ ).catch((error) => {
244
+ log("Fatal error:", error);
245
+ process.exit(1);
246
+ });
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "up-mcp-bridge",
3
+ "version": "1.0.0",
4
+ "description": "Remote proxy for MCP with auto-reconnect support. Fork of mcp-remote with transparent server restart handling.",
5
+ "keywords": [
6
+ "mcp",
7
+ "stdio",
8
+ "sse",
9
+ "remote",
10
+ "oauth",
11
+ "auto-reconnect",
12
+ "reconnection"
13
+ ],
14
+ "author": "César Obach <cesar.obach@ultrabase.net>",
15
+ "contributors": [
16
+ "Glen Maddern <glen@glenmaddern.com>"
17
+ ],
18
+ "license": "MIT",
19
+ "repository": "https://github.com/grupoultra/up-mcp-bridge",
20
+ "type": "module",
21
+ "files": [
22
+ "dist",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "main": "dist/index.js",
27
+ "bin": {
28
+ "up-mcp-bridge": "dist/proxy.js",
29
+ "up-mcp-bridge-client": "dist/client.js"
30
+ },
31
+ "scripts": {
32
+ "build": "tsup",
33
+ "build:watch": "tsup --watch",
34
+ "check": "prettier --check . && tsc",
35
+ "lint-fix": "prettier --check . --write",
36
+ "test:unit": "vitest run",
37
+ "test:unit:watch": "vitest"
38
+ },
39
+ "dependencies": {
40
+ "express": "^4.21.2",
41
+ "open": "^10.1.0",
42
+ "strict-url-sanitise": "^0.0.1",
43
+ "undici": "^7.12.0"
44
+ },
45
+ "devDependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.23.0",
47
+ "zod": "^4.0.0",
48
+ "@types/express": "^5.0.0",
49
+ "@types/node": "^22.13.10",
50
+ "prettier": "^3.5.3",
51
+ "tsup": "^8.4.0",
52
+ "tsx": "^4.19.3",
53
+ "typescript": "^5.8.2",
54
+ "vitest": "^3.2.3"
55
+ },
56
+ "tsup": {
57
+ "entry": [
58
+ "src/client.ts",
59
+ "src/proxy.ts"
60
+ ],
61
+ "format": [
62
+ "esm"
63
+ ],
64
+ "dts": true,
65
+ "clean": true,
66
+ "outDir": "dist",
67
+ "external": []
68
+ },
69
+ "packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
70
+ }