mcp-remote 0.0.13 → 0.0.15
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/{chunk-UL4C5N2N.js → chunk-R4LSJHXI.js} +212 -17
- package/dist/client.js +34 -31
- package/dist/proxy.js +10 -8
- package/package.json +2 -2
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
-
var
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
3
9
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
4
10
|
};
|
|
5
11
|
|
|
@@ -8,7 +14,7 @@ var require_package = __commonJS({
|
|
|
8
14
|
"package.json"(exports, module) {
|
|
9
15
|
module.exports = {
|
|
10
16
|
name: "mcp-remote",
|
|
11
|
-
version: "0.0.
|
|
17
|
+
version: "0.0.14",
|
|
12
18
|
description: "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth",
|
|
13
19
|
keywords: [
|
|
14
20
|
"mcp",
|
|
@@ -18,7 +24,7 @@ var require_package = __commonJS({
|
|
|
18
24
|
"oauth"
|
|
19
25
|
],
|
|
20
26
|
author: "Glen Maddern <glen@cloudflare.com>",
|
|
21
|
-
repository: "https://github.com/geelen/remote
|
|
27
|
+
repository: "https://github.com/geelen/mcp-remote",
|
|
22
28
|
type: "module",
|
|
23
29
|
files: [
|
|
24
30
|
"dist",
|
|
@@ -74,6 +80,8 @@ import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
|
74
80
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
75
81
|
import express from "express";
|
|
76
82
|
import net from "net";
|
|
83
|
+
import crypto from "crypto";
|
|
84
|
+
var MCP_REMOTE_VERSION = require_package().version;
|
|
77
85
|
var pid = process.pid;
|
|
78
86
|
function log(str, ...rest) {
|
|
79
87
|
console.error(`[${pid}] ${str}`, ...rest);
|
|
@@ -112,7 +120,7 @@ function mcpProxy({ transportToClient, transportToServer }) {
|
|
|
112
120
|
log("Error from remote server:", error);
|
|
113
121
|
}
|
|
114
122
|
}
|
|
115
|
-
async function connectToRemoteServer(serverUrl, authProvider, waitForAuthCode) {
|
|
123
|
+
async function connectToRemoteServer(serverUrl, authProvider, waitForAuthCode, skipBrowserAuth = false) {
|
|
116
124
|
log(`[${pid}] Connecting to remote server: ${serverUrl}`);
|
|
117
125
|
const url = new URL(serverUrl);
|
|
118
126
|
const transport = new SSEClientTransport(url, { authProvider });
|
|
@@ -122,7 +130,11 @@ async function connectToRemoteServer(serverUrl, authProvider, waitForAuthCode) {
|
|
|
122
130
|
return transport;
|
|
123
131
|
} catch (error) {
|
|
124
132
|
if (error instanceof UnauthorizedError || error instanceof Error && error.message.includes("Unauthorized")) {
|
|
125
|
-
|
|
133
|
+
if (skipBrowserAuth) {
|
|
134
|
+
log("Authentication required but skipping browser auth - using shared auth");
|
|
135
|
+
} else {
|
|
136
|
+
log("Authentication required. Waiting for authorization...");
|
|
137
|
+
}
|
|
126
138
|
const code = await waitForAuthCode();
|
|
127
139
|
try {
|
|
128
140
|
log("Completing authorization...");
|
|
@@ -141,9 +153,42 @@ async function connectToRemoteServer(serverUrl, authProvider, waitForAuthCode) {
|
|
|
141
153
|
}
|
|
142
154
|
}
|
|
143
155
|
}
|
|
144
|
-
function
|
|
156
|
+
function setupOAuthCallbackServerWithLongPoll(options) {
|
|
145
157
|
let authCode = null;
|
|
146
158
|
const app = express();
|
|
159
|
+
let authCompletedResolve;
|
|
160
|
+
const authCompletedPromise = new Promise((resolve) => {
|
|
161
|
+
authCompletedResolve = resolve;
|
|
162
|
+
});
|
|
163
|
+
app.get("/wait-for-auth", (req, res) => {
|
|
164
|
+
if (authCode) {
|
|
165
|
+
log("Auth already completed, returning 200");
|
|
166
|
+
res.status(200).send("Authentication completed");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (req.query.poll === "false") {
|
|
170
|
+
log("Client requested no long poll, responding with 202");
|
|
171
|
+
res.status(202).send("Authentication in progress");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const longPollTimeout = setTimeout(() => {
|
|
175
|
+
log("Long poll timeout reached, responding with 202");
|
|
176
|
+
res.status(202).send("Authentication in progress");
|
|
177
|
+
}, 3e4);
|
|
178
|
+
authCompletedPromise.then(() => {
|
|
179
|
+
clearTimeout(longPollTimeout);
|
|
180
|
+
if (!res.headersSent) {
|
|
181
|
+
log("Auth completed during long poll, responding with 200");
|
|
182
|
+
res.status(200).send("Authentication completed");
|
|
183
|
+
}
|
|
184
|
+
}).catch(() => {
|
|
185
|
+
clearTimeout(longPollTimeout);
|
|
186
|
+
if (!res.headersSent) {
|
|
187
|
+
log("Auth failed during long poll, responding with 500");
|
|
188
|
+
res.status(500).send("Authentication failed");
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
});
|
|
147
192
|
app.get(options.path, (req, res) => {
|
|
148
193
|
const code = req.query.code;
|
|
149
194
|
if (!code) {
|
|
@@ -151,6 +196,8 @@ function setupOAuthCallbackServer(options) {
|
|
|
151
196
|
return;
|
|
152
197
|
}
|
|
153
198
|
authCode = code;
|
|
199
|
+
log("Auth code received, resolving promise");
|
|
200
|
+
authCompletedResolve(code);
|
|
154
201
|
res.send("Authorization successful! You may close this window and return to the CLI.");
|
|
155
202
|
options.events.emit("auth-code-received", code);
|
|
156
203
|
});
|
|
@@ -168,7 +215,7 @@ function setupOAuthCallbackServer(options) {
|
|
|
168
215
|
});
|
|
169
216
|
});
|
|
170
217
|
};
|
|
171
|
-
return { server, authCode, waitForAuthCode };
|
|
218
|
+
return { server, authCode, waitForAuthCode, authCompletedPromise };
|
|
172
219
|
}
|
|
173
220
|
async function findAvailablePort(preferredPort) {
|
|
174
221
|
return new Promise((resolve, reject) => {
|
|
@@ -226,7 +273,9 @@ function setupSignalHandlers(cleanup) {
|
|
|
226
273
|
});
|
|
227
274
|
process.stdin.resume();
|
|
228
275
|
}
|
|
229
|
-
|
|
276
|
+
function getServerUrlHash(serverUrl) {
|
|
277
|
+
return crypto.createHash("md5").update(serverUrl).digest("hex");
|
|
278
|
+
}
|
|
230
279
|
|
|
231
280
|
// src/lib/node-oauth-client-provider.ts
|
|
232
281
|
import open from "open";
|
|
@@ -236,11 +285,37 @@ import {
|
|
|
236
285
|
} from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
237
286
|
|
|
238
287
|
// src/lib/mcp-auth-config.ts
|
|
239
|
-
import crypto from "crypto";
|
|
240
288
|
import path from "path";
|
|
241
289
|
import os from "os";
|
|
242
290
|
import fs from "fs/promises";
|
|
243
|
-
var knownConfigFiles = ["client_info.json", "tokens.json", "code_verifier.txt"];
|
|
291
|
+
var knownConfigFiles = ["client_info.json", "tokens.json", "code_verifier.txt", "lock.json"];
|
|
292
|
+
async function createLockfile(serverUrlHash, pid2, port) {
|
|
293
|
+
const lockData = {
|
|
294
|
+
pid: pid2,
|
|
295
|
+
port,
|
|
296
|
+
timestamp: Date.now()
|
|
297
|
+
};
|
|
298
|
+
await writeJsonFile(serverUrlHash, "lock.json", lockData);
|
|
299
|
+
}
|
|
300
|
+
async function checkLockfile(serverUrlHash) {
|
|
301
|
+
try {
|
|
302
|
+
const lockfile = await readJsonFile(serverUrlHash, "lock.json", {
|
|
303
|
+
async parseAsync(data) {
|
|
304
|
+
if (typeof data !== "object" || data === null) return null;
|
|
305
|
+
if (typeof data.pid !== "number" || typeof data.port !== "number" || typeof data.timestamp !== "number") {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
return data;
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
return lockfile || null;
|
|
312
|
+
} catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async function deleteLockfile(serverUrlHash) {
|
|
317
|
+
await deleteConfigFile(serverUrlHash, "lock.json");
|
|
318
|
+
}
|
|
244
319
|
async function cleanServerConfig(serverUrlHash) {
|
|
245
320
|
log(`Cleaning configuration files for server: ${serverUrlHash}`);
|
|
246
321
|
for (const filename of knownConfigFiles) {
|
|
@@ -260,9 +335,6 @@ async function ensureConfigDir() {
|
|
|
260
335
|
throw error;
|
|
261
336
|
}
|
|
262
337
|
}
|
|
263
|
-
function getServerUrlHash(serverUrl) {
|
|
264
|
-
return crypto.createHash("md5").update(serverUrl).digest("hex");
|
|
265
|
-
}
|
|
266
338
|
function getConfigFilePath(serverUrlHash, filename) {
|
|
267
339
|
const configDir = getConfigDir();
|
|
268
340
|
return path.join(configDir, `${serverUrlHash}_${filename}`);
|
|
@@ -286,11 +358,13 @@ async function readJsonFile(serverUrlHash, filename, schema, clean = false) {
|
|
|
286
358
|
}
|
|
287
359
|
const filePath = getConfigFilePath(serverUrlHash, filename);
|
|
288
360
|
const content = await fs.readFile(filePath, "utf-8");
|
|
289
|
-
|
|
361
|
+
const result = await schema.parseAsync(JSON.parse(content));
|
|
362
|
+
return result;
|
|
290
363
|
} catch (error) {
|
|
291
364
|
if (error.code === "ENOENT") {
|
|
292
365
|
return void 0;
|
|
293
366
|
}
|
|
367
|
+
log(`Error reading ${filename}:`, error);
|
|
294
368
|
return void 0;
|
|
295
369
|
}
|
|
296
370
|
}
|
|
@@ -423,13 +497,134 @@ ${authorizationUrl.toString()}
|
|
|
423
497
|
}
|
|
424
498
|
};
|
|
425
499
|
|
|
500
|
+
// src/lib/coordination.ts
|
|
501
|
+
import express2 from "express";
|
|
502
|
+
async function isPidRunning(pid2) {
|
|
503
|
+
try {
|
|
504
|
+
process.kill(pid2, 0);
|
|
505
|
+
return true;
|
|
506
|
+
} catch {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async function isLockValid(lockData) {
|
|
511
|
+
const MAX_LOCK_AGE = 30 * 60 * 1e3;
|
|
512
|
+
if (Date.now() - lockData.timestamp > MAX_LOCK_AGE) {
|
|
513
|
+
log("Lockfile is too old");
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
if (!await isPidRunning(lockData.pid)) {
|
|
517
|
+
log("Process from lockfile is not running");
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
const controller = new AbortController();
|
|
522
|
+
const timeout = setTimeout(() => controller.abort(), 1e3);
|
|
523
|
+
const response = await fetch(`http://127.0.0.1:${lockData.port}/wait-for-auth?poll=false`, {
|
|
524
|
+
signal: controller.signal
|
|
525
|
+
});
|
|
526
|
+
clearTimeout(timeout);
|
|
527
|
+
return response.status === 200 || response.status === 202;
|
|
528
|
+
} catch (error) {
|
|
529
|
+
log(`Error connecting to auth server: ${error.message}`);
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
async function waitForAuthentication(port) {
|
|
534
|
+
log(`Waiting for authentication from the server on port ${port}...`);
|
|
535
|
+
try {
|
|
536
|
+
while (true) {
|
|
537
|
+
const url = `http://127.0.0.1:${port}/wait-for-auth`;
|
|
538
|
+
log(`Querying: ${url}`);
|
|
539
|
+
const response = await fetch(url);
|
|
540
|
+
if (response.status === 200) {
|
|
541
|
+
log(`Authentication completed by other instance`);
|
|
542
|
+
return true;
|
|
543
|
+
} else if (response.status === 202) {
|
|
544
|
+
log(`Authentication still in progress`);
|
|
545
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
546
|
+
} else {
|
|
547
|
+
log(`Unexpected response status: ${response.status}`);
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
} catch (error) {
|
|
552
|
+
log(`Error waiting for authentication: ${error.message}`);
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
async function coordinateAuth(serverUrlHash, callbackPort, events) {
|
|
557
|
+
const lockData = await checkLockfile(serverUrlHash);
|
|
558
|
+
if (lockData && await isLockValid(lockData)) {
|
|
559
|
+
log(`Another instance is handling authentication on port ${lockData.port}`);
|
|
560
|
+
try {
|
|
561
|
+
const authCompleted = await waitForAuthentication(lockData.port);
|
|
562
|
+
if (authCompleted) {
|
|
563
|
+
log("Authentication completed by another instance");
|
|
564
|
+
const dummyServer = express2().listen(0);
|
|
565
|
+
const dummyWaitForAuthCode = () => {
|
|
566
|
+
log("WARNING: waitForAuthCode called in secondary instance - this is unexpected");
|
|
567
|
+
return new Promise(() => {
|
|
568
|
+
});
|
|
569
|
+
};
|
|
570
|
+
return {
|
|
571
|
+
server: dummyServer,
|
|
572
|
+
waitForAuthCode: dummyWaitForAuthCode,
|
|
573
|
+
skipBrowserAuth: true
|
|
574
|
+
};
|
|
575
|
+
} else {
|
|
576
|
+
log("Taking over authentication process...");
|
|
577
|
+
}
|
|
578
|
+
} catch (error) {
|
|
579
|
+
log(`Error waiting for authentication: ${error}`);
|
|
580
|
+
}
|
|
581
|
+
await deleteLockfile(serverUrlHash);
|
|
582
|
+
} else if (lockData) {
|
|
583
|
+
log("Found invalid lockfile, deleting it");
|
|
584
|
+
await deleteLockfile(serverUrlHash);
|
|
585
|
+
}
|
|
586
|
+
const { server, waitForAuthCode, authCompletedPromise } = setupOAuthCallbackServerWithLongPoll({
|
|
587
|
+
port: callbackPort,
|
|
588
|
+
path: "/oauth/callback",
|
|
589
|
+
events
|
|
590
|
+
});
|
|
591
|
+
const address = server.address();
|
|
592
|
+
const actualPort = address.port;
|
|
593
|
+
log(`Creating lockfile for server ${serverUrlHash} with process ${process.pid} on port ${actualPort}`);
|
|
594
|
+
await createLockfile(serverUrlHash, process.pid, actualPort);
|
|
595
|
+
const cleanupHandler = async () => {
|
|
596
|
+
try {
|
|
597
|
+
log(`Cleaning up lockfile for server ${serverUrlHash}`);
|
|
598
|
+
await deleteLockfile(serverUrlHash);
|
|
599
|
+
} catch (error) {
|
|
600
|
+
log(`Error cleaning up lockfile: ${error}`);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
process.once("exit", () => {
|
|
604
|
+
try {
|
|
605
|
+
const configPath = getConfigFilePath(serverUrlHash, "lock.json");
|
|
606
|
+
__require("fs").unlinkSync(configPath);
|
|
607
|
+
} catch {
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
process.once("SIGINT", async () => {
|
|
611
|
+
await cleanupHandler();
|
|
612
|
+
});
|
|
613
|
+
return {
|
|
614
|
+
server,
|
|
615
|
+
waitForAuthCode,
|
|
616
|
+
skipBrowserAuth: false
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
426
620
|
export {
|
|
621
|
+
MCP_REMOTE_VERSION,
|
|
427
622
|
log,
|
|
428
623
|
mcpProxy,
|
|
429
624
|
connectToRemoteServer,
|
|
430
|
-
setupOAuthCallbackServer,
|
|
431
625
|
parseCommandLineArgs,
|
|
432
626
|
setupSignalHandlers,
|
|
433
|
-
|
|
434
|
-
NodeOAuthClientProvider
|
|
627
|
+
getServerUrlHash,
|
|
628
|
+
NodeOAuthClientProvider,
|
|
629
|
+
coordinateAuth
|
|
435
630
|
};
|
package/dist/client.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
MCP_REMOTE_VERSION,
|
|
4
4
|
NodeOAuthClientProvider,
|
|
5
|
+
coordinateAuth,
|
|
6
|
+
getServerUrlHash,
|
|
7
|
+
log,
|
|
5
8
|
parseCommandLineArgs,
|
|
6
|
-
setupOAuthCallbackServer,
|
|
7
9
|
setupSignalHandlers
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-R4LSJHXI.js";
|
|
9
11
|
|
|
10
12
|
// src/client.ts
|
|
11
13
|
import { EventEmitter } from "events";
|
|
@@ -15,12 +17,18 @@ import { ListResourcesResultSchema, ListToolsResultSchema } from "@modelcontextp
|
|
|
15
17
|
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
16
18
|
async function runClient(serverUrl, callbackPort, clean = false) {
|
|
17
19
|
const events = new EventEmitter();
|
|
20
|
+
const serverUrlHash = getServerUrlHash(serverUrl);
|
|
21
|
+
const { server, waitForAuthCode, skipBrowserAuth } = await coordinateAuth(serverUrlHash, callbackPort, events);
|
|
18
22
|
const authProvider = new NodeOAuthClientProvider({
|
|
19
23
|
serverUrl,
|
|
20
24
|
callbackPort,
|
|
21
25
|
clientName: "MCP CLI Client",
|
|
22
26
|
clean
|
|
23
27
|
});
|
|
28
|
+
if (skipBrowserAuth) {
|
|
29
|
+
log("Authentication was completed by another instance - will use tokens from disk...");
|
|
30
|
+
await new Promise((res) => setTimeout(res, 1e3));
|
|
31
|
+
}
|
|
24
32
|
const client = new Client(
|
|
25
33
|
{
|
|
26
34
|
name: "mcp-remote",
|
|
@@ -34,76 +42,71 @@ async function runClient(serverUrl, callbackPort, clean = false) {
|
|
|
34
42
|
function initTransport() {
|
|
35
43
|
const transport2 = new SSEClientTransport(url, { authProvider });
|
|
36
44
|
transport2.onmessage = (message) => {
|
|
37
|
-
|
|
45
|
+
log("Received message:", JSON.stringify(message, null, 2));
|
|
38
46
|
};
|
|
39
47
|
transport2.onerror = (error) => {
|
|
40
|
-
|
|
48
|
+
log("Transport error:", error);
|
|
41
49
|
};
|
|
42
50
|
transport2.onclose = () => {
|
|
43
|
-
|
|
51
|
+
log("Connection closed.");
|
|
44
52
|
process.exit(0);
|
|
45
53
|
};
|
|
46
54
|
return transport2;
|
|
47
55
|
}
|
|
48
56
|
const transport = initTransport();
|
|
49
|
-
const { server, waitForAuthCode } = setupOAuthCallbackServer({
|
|
50
|
-
port: callbackPort,
|
|
51
|
-
path: "/oauth/callback",
|
|
52
|
-
events
|
|
53
|
-
});
|
|
54
57
|
const cleanup = async () => {
|
|
55
|
-
|
|
58
|
+
log("\nClosing connection...");
|
|
56
59
|
await client.close();
|
|
57
60
|
server.close();
|
|
58
61
|
};
|
|
59
62
|
setupSignalHandlers(cleanup);
|
|
60
63
|
try {
|
|
61
|
-
|
|
64
|
+
log("Connecting to server...");
|
|
62
65
|
await client.connect(transport);
|
|
63
|
-
|
|
66
|
+
log("Connected successfully!");
|
|
64
67
|
} catch (error) {
|
|
65
68
|
if (error instanceof UnauthorizedError || error instanceof Error && error.message.includes("Unauthorized")) {
|
|
66
|
-
|
|
69
|
+
log("Authentication required. Waiting for authorization...");
|
|
67
70
|
const code = await waitForAuthCode();
|
|
68
71
|
try {
|
|
69
|
-
|
|
72
|
+
log("Completing authorization...");
|
|
70
73
|
await transport.finishAuth(code);
|
|
71
|
-
|
|
74
|
+
log("Connecting after authorization...");
|
|
72
75
|
await client.connect(initTransport());
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
log("Connected successfully!");
|
|
77
|
+
log("Requesting tools list...");
|
|
75
78
|
const tools = await client.request({ method: "tools/list" }, ListToolsResultSchema);
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
log("Tools:", JSON.stringify(tools, null, 2));
|
|
80
|
+
log("Requesting resource list...");
|
|
78
81
|
const resources = await client.request({ method: "resources/list" }, ListResourcesResultSchema);
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
log("Resources:", JSON.stringify(resources, null, 2));
|
|
83
|
+
log("Listening for messages. Press Ctrl+C to exit.");
|
|
81
84
|
} catch (authError) {
|
|
82
|
-
|
|
85
|
+
log("Authorization error:", authError);
|
|
83
86
|
server.close();
|
|
84
87
|
process.exit(1);
|
|
85
88
|
}
|
|
86
89
|
} else {
|
|
87
|
-
|
|
90
|
+
log("Connection error:", error);
|
|
88
91
|
server.close();
|
|
89
92
|
process.exit(1);
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
95
|
try {
|
|
93
|
-
|
|
96
|
+
log("Requesting tools list...");
|
|
94
97
|
const tools = await client.request({ method: "tools/list" }, ListToolsResultSchema);
|
|
95
|
-
|
|
98
|
+
log("Tools:", JSON.stringify(tools, null, 2));
|
|
96
99
|
} catch (e) {
|
|
97
|
-
|
|
100
|
+
log("Error requesting tools list:", e);
|
|
98
101
|
}
|
|
99
102
|
try {
|
|
100
|
-
|
|
103
|
+
log("Requesting resource list...");
|
|
101
104
|
const resources = await client.request({ method: "resources/list" }, ListResourcesResultSchema);
|
|
102
|
-
|
|
105
|
+
log("Resources:", JSON.stringify(resources, null, 2));
|
|
103
106
|
} catch (e) {
|
|
104
|
-
|
|
107
|
+
log("Error requesting resources list:", e);
|
|
105
108
|
}
|
|
106
|
-
|
|
109
|
+
log("Listening for messages. Press Ctrl+C to exit.");
|
|
107
110
|
}
|
|
108
111
|
parseCommandLineArgs(process.argv.slice(2), 3333, "Usage: npx tsx client.ts [--clean] <https://server-url> [callback-port]").then(({ serverUrl, callbackPort, clean }) => {
|
|
109
112
|
return runClient(serverUrl, callbackPort, clean);
|
package/dist/proxy.js
CHANGED
|
@@ -2,32 +2,34 @@
|
|
|
2
2
|
import {
|
|
3
3
|
NodeOAuthClientProvider,
|
|
4
4
|
connectToRemoteServer,
|
|
5
|
+
coordinateAuth,
|
|
6
|
+
getServerUrlHash,
|
|
5
7
|
log,
|
|
6
8
|
mcpProxy,
|
|
7
9
|
parseCommandLineArgs,
|
|
8
|
-
setupOAuthCallbackServer,
|
|
9
10
|
setupSignalHandlers
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-R4LSJHXI.js";
|
|
11
12
|
|
|
12
13
|
// src/proxy.ts
|
|
13
14
|
import { EventEmitter } from "events";
|
|
14
15
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
16
|
async function runProxy(serverUrl, callbackPort, clean = false) {
|
|
16
17
|
const events = new EventEmitter();
|
|
18
|
+
const serverUrlHash = getServerUrlHash(serverUrl);
|
|
19
|
+
const { server, waitForAuthCode, skipBrowserAuth } = await coordinateAuth(serverUrlHash, callbackPort, events);
|
|
17
20
|
const authProvider = new NodeOAuthClientProvider({
|
|
18
21
|
serverUrl,
|
|
19
22
|
callbackPort,
|
|
20
23
|
clientName: "MCP CLI Proxy",
|
|
21
24
|
clean
|
|
22
25
|
});
|
|
26
|
+
if (skipBrowserAuth) {
|
|
27
|
+
log("Authentication was completed by another instance - will use tokens from disk");
|
|
28
|
+
await new Promise((res) => setTimeout(res, 1e3));
|
|
29
|
+
}
|
|
23
30
|
const localTransport = new StdioServerTransport();
|
|
24
|
-
const { server, waitForAuthCode } = setupOAuthCallbackServer({
|
|
25
|
-
port: callbackPort,
|
|
26
|
-
path: "/oauth/callback",
|
|
27
|
-
events
|
|
28
|
-
});
|
|
29
31
|
try {
|
|
30
|
-
const remoteTransport = await connectToRemoteServer(serverUrl, authProvider, waitForAuthCode);
|
|
32
|
+
const remoteTransport = await connectToRemoteServer(serverUrl, authProvider, waitForAuthCode, skipBrowserAuth);
|
|
31
33
|
mcpProxy({
|
|
32
34
|
transportToClient: localTransport,
|
|
33
35
|
transportToServer: remoteTransport
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-remote",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"oauth"
|
|
11
11
|
],
|
|
12
12
|
"author": "Glen Maddern <glen@cloudflare.com>",
|
|
13
|
-
"repository": "https://github.com/geelen/remote
|
|
13
|
+
"repository": "https://github.com/geelen/mcp-remote",
|
|
14
14
|
"type": "module",
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|