ma-mcp-remote 0.1.41
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/LICENSE +21 -0
- package/README.md +400 -0
- package/dist/chunk-AO2MMDOT.js +24119 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +143 -0
- package/dist/proxy.d.ts +1 -0
- package/dist/proxy.js +234 -0
- package/package.json +65 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
Client,
|
|
4
|
+
ListResourcesResultSchema,
|
|
5
|
+
ListToolsResultSchema,
|
|
6
|
+
NodeOAuthClientProvider,
|
|
7
|
+
connectToRemoteServer,
|
|
8
|
+
createLazyAuthCoordinator,
|
|
9
|
+
debugLog,
|
|
10
|
+
discoverOAuthServerInfo,
|
|
11
|
+
log,
|
|
12
|
+
parseCommandLineArgs,
|
|
13
|
+
setupSignalHandlers,
|
|
14
|
+
version
|
|
15
|
+
} from "./chunk-AO2MMDOT.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
|
+
log("Discovering OAuth server configuration...");
|
|
23
|
+
const discoveryResult = await discoverOAuthServerInfo(serverUrl, headers);
|
|
24
|
+
if (discoveryResult.protectedResourceMetadata) {
|
|
25
|
+
log(`Discovered authorization server: ${discoveryResult.authorizationServerUrl}`);
|
|
26
|
+
if (discoveryResult.protectedResourceMetadata.scopes_supported) {
|
|
27
|
+
debugLog("Protected Resource Metadata scopes", {
|
|
28
|
+
scopes_supported: discoveryResult.protectedResourceMetadata.scopes_supported
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
debugLog("No Protected Resource Metadata found, using server URL as authorization server");
|
|
33
|
+
}
|
|
34
|
+
const authProvider = new NodeOAuthClientProvider({
|
|
35
|
+
serverUrl: discoveryResult.authorizationServerUrl,
|
|
36
|
+
callbackPort,
|
|
37
|
+
host,
|
|
38
|
+
clientName: "MCP CLI Client",
|
|
39
|
+
staticOAuthClientMetadata,
|
|
40
|
+
staticOAuthClientInfo,
|
|
41
|
+
serverUrlHash,
|
|
42
|
+
authorizationServerMetadata: discoveryResult.authorizationServerMetadata,
|
|
43
|
+
protectedResourceMetadata: discoveryResult.protectedResourceMetadata,
|
|
44
|
+
wwwAuthenticateScope: discoveryResult.wwwAuthenticateScope
|
|
45
|
+
});
|
|
46
|
+
const client = new Client(
|
|
47
|
+
{
|
|
48
|
+
name: "mcp-remote",
|
|
49
|
+
version
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
capabilities: {}
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
let server = null;
|
|
56
|
+
const authInitializer = async () => {
|
|
57
|
+
const authState = await authCoordinator.initializeAuth();
|
|
58
|
+
server = authState.server;
|
|
59
|
+
if (authState.skipBrowserAuth) {
|
|
60
|
+
log("Authentication was completed by another instance - will use tokens from disk...");
|
|
61
|
+
await new Promise((res) => setTimeout(res, 1e3));
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
waitForAuthCode: authState.waitForAuthCode,
|
|
65
|
+
skipBrowserAuth: authState.skipBrowserAuth
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
try {
|
|
69
|
+
const transport = await connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy);
|
|
70
|
+
transport.onmessage = (message) => {
|
|
71
|
+
log("Received message:", JSON.stringify(message, null, 2));
|
|
72
|
+
};
|
|
73
|
+
transport.onerror = (error) => {
|
|
74
|
+
log("Transport error:", error);
|
|
75
|
+
};
|
|
76
|
+
transport.onclose = () => {
|
|
77
|
+
log("Connection closed.");
|
|
78
|
+
process.exit(0);
|
|
79
|
+
};
|
|
80
|
+
const cleanup = async () => {
|
|
81
|
+
log("\nClosing connection...");
|
|
82
|
+
await client.close();
|
|
83
|
+
if (server) {
|
|
84
|
+
server.close();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
setupSignalHandlers(cleanup);
|
|
88
|
+
log("Connected successfully!");
|
|
89
|
+
try {
|
|
90
|
+
log("Requesting tools list...");
|
|
91
|
+
const tools = await client.request({ method: "tools/list" }, ListToolsResultSchema);
|
|
92
|
+
log("Tools:", JSON.stringify(tools, null, 2));
|
|
93
|
+
} catch (e) {
|
|
94
|
+
log("Error requesting tools list:", e);
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
log("Requesting resource list...");
|
|
98
|
+
const resources = await client.request({ method: "resources/list" }, ListResourcesResultSchema);
|
|
99
|
+
log("Resources:", JSON.stringify(resources, null, 2));
|
|
100
|
+
} catch (e) {
|
|
101
|
+
log("Error requesting resources list:", e);
|
|
102
|
+
}
|
|
103
|
+
log("Exiting OK...");
|
|
104
|
+
if (server) {
|
|
105
|
+
server.close();
|
|
106
|
+
}
|
|
107
|
+
process.exit(0);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
log("Fatal error:", error);
|
|
110
|
+
if (server) {
|
|
111
|
+
server.close();
|
|
112
|
+
}
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
parseCommandLineArgs(process.argv.slice(2), "Usage: npx tsx client.ts <https://server-url> [callback-port] [--debug]").then(
|
|
117
|
+
({
|
|
118
|
+
serverUrl,
|
|
119
|
+
callbackPort,
|
|
120
|
+
headers,
|
|
121
|
+
transportStrategy,
|
|
122
|
+
host,
|
|
123
|
+
staticOAuthClientMetadata,
|
|
124
|
+
staticOAuthClientInfo,
|
|
125
|
+
authTimeoutMs,
|
|
126
|
+
serverUrlHash
|
|
127
|
+
}) => {
|
|
128
|
+
return runClient(
|
|
129
|
+
serverUrl,
|
|
130
|
+
callbackPort,
|
|
131
|
+
headers,
|
|
132
|
+
transportStrategy,
|
|
133
|
+
host,
|
|
134
|
+
staticOAuthClientMetadata,
|
|
135
|
+
staticOAuthClientInfo,
|
|
136
|
+
authTimeoutMs,
|
|
137
|
+
serverUrlHash
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
).catch((error) => {
|
|
141
|
+
console.error("Fatal error:", error);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
});
|
package/dist/proxy.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
JSONRPCMessageSchema,
|
|
4
|
+
NodeOAuthClientProvider,
|
|
5
|
+
connectToRemoteServer,
|
|
6
|
+
createLazyAuthCoordinator,
|
|
7
|
+
debugLog,
|
|
8
|
+
discoverOAuthServerInfo,
|
|
9
|
+
log,
|
|
10
|
+
mcpProxy,
|
|
11
|
+
parseCommandLineArgs,
|
|
12
|
+
setupSignalHandlers
|
|
13
|
+
} from "./chunk-AO2MMDOT.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
|
+
this.onerror?.(error);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Starts listening for messages on stdin.
|
|
66
|
+
*/
|
|
67
|
+
async start() {
|
|
68
|
+
if (this._started) {
|
|
69
|
+
throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");
|
|
70
|
+
}
|
|
71
|
+
this._started = true;
|
|
72
|
+
this._stdin.on("data", this._ondata);
|
|
73
|
+
this._stdin.on("error", this._onerror);
|
|
74
|
+
}
|
|
75
|
+
processReadBuffer() {
|
|
76
|
+
while (true) {
|
|
77
|
+
try {
|
|
78
|
+
const message = this._readBuffer.readMessage();
|
|
79
|
+
if (message === null) {
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
this.onmessage?.(message);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
this.onerror?.(error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async close() {
|
|
89
|
+
this._stdin.off("data", this._ondata);
|
|
90
|
+
this._stdin.off("error", this._onerror);
|
|
91
|
+
const remainingDataListeners = this._stdin.listenerCount("data");
|
|
92
|
+
if (remainingDataListeners === 0) {
|
|
93
|
+
this._stdin.pause();
|
|
94
|
+
}
|
|
95
|
+
this._readBuffer.clear();
|
|
96
|
+
this.onclose?.();
|
|
97
|
+
}
|
|
98
|
+
send(message) {
|
|
99
|
+
return new Promise((resolve) => {
|
|
100
|
+
const json = serializeMessage(message);
|
|
101
|
+
if (this._stdout.write(json)) {
|
|
102
|
+
resolve();
|
|
103
|
+
} else {
|
|
104
|
+
this._stdout.once("drain", resolve);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// src/proxy.ts
|
|
111
|
+
async function runProxy(serverUrl, callbackPort, headers, transportStrategy = "http-first", host, staticOAuthClientMetadata, staticOAuthClientInfo, authorizeResource, ignoredTools, authTimeoutMs, serverUrlHash) {
|
|
112
|
+
const events = new EventEmitter();
|
|
113
|
+
const authCoordinator = createLazyAuthCoordinator(serverUrlHash, callbackPort, events, authTimeoutMs);
|
|
114
|
+
log("Discovering OAuth server configuration...");
|
|
115
|
+
const discoveryResult = await discoverOAuthServerInfo(serverUrl, headers);
|
|
116
|
+
if (discoveryResult.protectedResourceMetadata) {
|
|
117
|
+
log(`Discovered authorization server: ${discoveryResult.authorizationServerUrl}`);
|
|
118
|
+
if (discoveryResult.protectedResourceMetadata.scopes_supported) {
|
|
119
|
+
debugLog("Protected Resource Metadata scopes", {
|
|
120
|
+
scopes_supported: discoveryResult.protectedResourceMetadata.scopes_supported
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
debugLog("No Protected Resource Metadata found, using server URL as authorization server");
|
|
125
|
+
}
|
|
126
|
+
const authProvider = new NodeOAuthClientProvider({
|
|
127
|
+
serverUrl: discoveryResult.authorizationServerUrl,
|
|
128
|
+
callbackPort,
|
|
129
|
+
host,
|
|
130
|
+
clientName: "MCP CLI Proxy",
|
|
131
|
+
staticOAuthClientMetadata,
|
|
132
|
+
staticOAuthClientInfo,
|
|
133
|
+
authorizeResource,
|
|
134
|
+
serverUrlHash,
|
|
135
|
+
authorizationServerMetadata: discoveryResult.authorizationServerMetadata,
|
|
136
|
+
protectedResourceMetadata: discoveryResult.protectedResourceMetadata,
|
|
137
|
+
wwwAuthenticateScope: discoveryResult.wwwAuthenticateScope
|
|
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
|
+
mcpProxy({
|
|
156
|
+
transportToClient: localTransport,
|
|
157
|
+
transportToServer: remoteTransport,
|
|
158
|
+
ignoredTools
|
|
159
|
+
});
|
|
160
|
+
await localTransport.start();
|
|
161
|
+
log("Local STDIO server running");
|
|
162
|
+
log(`Proxy established successfully between local STDIO and remote ${remoteTransport.constructor.name}`);
|
|
163
|
+
log("Press Ctrl+C to exit");
|
|
164
|
+
const cleanup = async () => {
|
|
165
|
+
await remoteTransport.close();
|
|
166
|
+
await localTransport.close();
|
|
167
|
+
if (server) {
|
|
168
|
+
server.close();
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
setupSignalHandlers(cleanup);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
log("Fatal error:", error);
|
|
174
|
+
if (error instanceof Error && error.message.includes("self-signed certificate in certificate chain")) {
|
|
175
|
+
log(`You may be behind a VPN!
|
|
176
|
+
|
|
177
|
+
If you are behind a VPN, you can try setting the NODE_EXTRA_CA_CERTS environment variable to point
|
|
178
|
+
to the CA certificate file. If using claude_desktop_config.json, this might look like:
|
|
179
|
+
|
|
180
|
+
{
|
|
181
|
+
"mcpServers": {
|
|
182
|
+
"\${mcpServerName}": {
|
|
183
|
+
"command": "npx",
|
|
184
|
+
"args": [
|
|
185
|
+
"mcp-remote",
|
|
186
|
+
"https://remote.mcp.server/sse"
|
|
187
|
+
],
|
|
188
|
+
"env": {
|
|
189
|
+
"NODE_EXTRA_CA_CERTS": "\${your CA certificate file path}.pem"
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
`);
|
|
195
|
+
}
|
|
196
|
+
if (server) {
|
|
197
|
+
server.close();
|
|
198
|
+
}
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
parseCommandLineArgs(process.argv.slice(2), "Usage: npx tsx proxy.ts <https://server-url> [callback-port] [--debug]").then(
|
|
203
|
+
({
|
|
204
|
+
serverUrl,
|
|
205
|
+
callbackPort,
|
|
206
|
+
headers,
|
|
207
|
+
transportStrategy,
|
|
208
|
+
host,
|
|
209
|
+
debug,
|
|
210
|
+
staticOAuthClientMetadata,
|
|
211
|
+
staticOAuthClientInfo,
|
|
212
|
+
authorizeResource,
|
|
213
|
+
ignoredTools,
|
|
214
|
+
authTimeoutMs,
|
|
215
|
+
serverUrlHash
|
|
216
|
+
}) => {
|
|
217
|
+
return runProxy(
|
|
218
|
+
serverUrl,
|
|
219
|
+
callbackPort,
|
|
220
|
+
headers,
|
|
221
|
+
transportStrategy,
|
|
222
|
+
host,
|
|
223
|
+
staticOAuthClientMetadata,
|
|
224
|
+
staticOAuthClientInfo,
|
|
225
|
+
authorizeResource,
|
|
226
|
+
ignoredTools,
|
|
227
|
+
authTimeoutMs,
|
|
228
|
+
serverUrlHash
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
).catch((error) => {
|
|
232
|
+
log("Fatal error:", error);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ma-mcp-remote",
|
|
3
|
+
"version": "0.1.41",
|
|
4
|
+
"description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"stdio",
|
|
8
|
+
"sse",
|
|
9
|
+
"remote",
|
|
10
|
+
"oauth"
|
|
11
|
+
],
|
|
12
|
+
"author": "tomerr",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": "https://github.com/geelen/mcp-remote",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"main": "dist/index.js",
|
|
22
|
+
"bin": {
|
|
23
|
+
"mcp-remote": "dist/proxy.js",
|
|
24
|
+
"mcp-remote-client": "dist/client.js"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"build:watch": "tsup --watch",
|
|
29
|
+
"check": "prettier --check . && tsc",
|
|
30
|
+
"lint-fix": "prettier --check . --write",
|
|
31
|
+
"test:unit": "vitest run",
|
|
32
|
+
"test:unit:watch": "vitest"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"express": "^4.21.2",
|
|
36
|
+
"open": "^10.1.0",
|
|
37
|
+
"strict-url-sanitise": "^0.0.1",
|
|
38
|
+
"undici": "^7.12.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.23.0",
|
|
42
|
+
"zod": "^4.0.0",
|
|
43
|
+
"@types/express": "^5.0.0",
|
|
44
|
+
"@types/node": "^22.13.10",
|
|
45
|
+
"prettier": "^3.5.3",
|
|
46
|
+
"tsup": "^8.4.0",
|
|
47
|
+
"tsx": "^4.19.3",
|
|
48
|
+
"typescript": "^5.8.2",
|
|
49
|
+
"vitest": "^3.2.3"
|
|
50
|
+
},
|
|
51
|
+
"tsup": {
|
|
52
|
+
"entry": [
|
|
53
|
+
"src/client.ts",
|
|
54
|
+
"src/proxy.ts"
|
|
55
|
+
],
|
|
56
|
+
"format": [
|
|
57
|
+
"esm"
|
|
58
|
+
],
|
|
59
|
+
"dts": true,
|
|
60
|
+
"clean": true,
|
|
61
|
+
"outDir": "dist",
|
|
62
|
+
"external": []
|
|
63
|
+
},
|
|
64
|
+
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
|
|
65
|
+
}
|