mcp-secure-remote 0.0.1__tar.gz
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.
- mcp_secure_remote-0.0.1/LICENSE +21 -0
- mcp_secure_remote-0.0.1/PKG-INFO +673 -0
- mcp_secure_remote-0.0.1/README.md +651 -0
- mcp_secure_remote-0.0.1/pyproject.toml +49 -0
- mcp_secure_remote-0.0.1/setup.cfg +4 -0
- mcp_secure_remote-0.0.1/src/mcp_secure_remote/__init__.py +0 -0
- mcp_secure_remote-0.0.1/src/mcp_secure_remote/args.py +223 -0
- mcp_secure_remote-0.0.1/src/mcp_secure_remote/client.py +78 -0
- mcp_secure_remote-0.0.1/src/mcp_secure_remote/log.py +42 -0
- mcp_secure_remote-0.0.1/src/mcp_secure_remote/mtls.py +123 -0
- mcp_secure_remote-0.0.1/src/mcp_secure_remote/proxy.py +76 -0
- mcp_secure_remote-0.0.1/src/mcp_secure_remote/sanitize.py +125 -0
- mcp_secure_remote-0.0.1/src/mcp_secure_remote/transport.py +180 -0
- mcp_secure_remote-0.0.1/src/mcp_secure_remote.egg-info/PKG-INFO +673 -0
- mcp_secure_remote-0.0.1/src/mcp_secure_remote.egg-info/SOURCES.txt +22 -0
- mcp_secure_remote-0.0.1/src/mcp_secure_remote.egg-info/dependency_links.txt +1 -0
- mcp_secure_remote-0.0.1/src/mcp_secure_remote.egg-info/entry_points.txt +3 -0
- mcp_secure_remote-0.0.1/src/mcp_secure_remote.egg-info/requires.txt +8 -0
- mcp_secure_remote-0.0.1/src/mcp_secure_remote.egg-info/top_level.txt +1 -0
- mcp_secure_remote-0.0.1/tests/test_args.py +391 -0
- mcp_secure_remote-0.0.1/tests/test_log.py +126 -0
- mcp_secure_remote-0.0.1/tests/test_mtls.py +178 -0
- mcp_secure_remote-0.0.1/tests/test_sanitize.py +274 -0
- mcp_secure_remote-0.0.1/tests/test_transport.py +340 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 framedparadox
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcp-secure-remote
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Remote proxy for Model Context Protocol servers with mTLS (mutual TLS) client certificate authentication support.
|
|
5
|
+
Author: framedparadox
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/framedparadox/mcp-secure-remote#readme
|
|
8
|
+
Project-URL: Repository, https://github.com/framedparadox/mcp-secure-remote.git
|
|
9
|
+
Project-URL: Issues, https://github.com/framedparadox/mcp-secure-remote/issues
|
|
10
|
+
Keywords: mcp,model-context-protocol,mtls,mutual-tls,proxy,remote,stdio
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: mcp>=1.0.0
|
|
15
|
+
Requires-Dist: httpx>=0.25.0
|
|
16
|
+
Requires-Dist: anyio>=4.0.0
|
|
17
|
+
Requires-Dist: cryptography>=41.0.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
20
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# mcp-secure-remote
|
|
24
|
+
|
|
25
|
+
A stdio ↔ remote bridge for the [Model Context Protocol](https://modelcontextprotocol.io)
|
|
26
|
+
with first-class **mTLS (mutual TLS) client-certificate authentication**.
|
|
27
|
+
|
|
28
|
+
Run directly with [`uvx`](https://docs.astral.sh/uv/guides/tools/) — no install step needed:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uvx mcp-secure-remote https://mcp.example.com/mcp \
|
|
32
|
+
--tls-cert ./certs/client.crt \
|
|
33
|
+
--tls-key ./certs/client.key \
|
|
34
|
+
--tls-ca ./certs/ca-bundle.pem
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Works with any MCP-capable AI agent or IDE — Claude Desktop, Claude Code,
|
|
38
|
+
Cursor, Windsurf, Cline, Continue, Zed, and any custom client that speaks
|
|
39
|
+
the MCP stdio transport.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Contents
|
|
44
|
+
|
|
45
|
+
1. [What it does](#what-it-does)
|
|
46
|
+
2. [How it works](#how-it-works)
|
|
47
|
+
3. [Prerequisites](#prerequisites)
|
|
48
|
+
4. [Install](#install)
|
|
49
|
+
5. [Docker](#docker)
|
|
50
|
+
6. [Generate or obtain client certificates](#generate-or-obtain-client-certificates)
|
|
51
|
+
7. [Quick start](#quick-start)
|
|
52
|
+
8. [CLI parameters](#cli-parameters)
|
|
53
|
+
9. [Environment variables](#environment-variables)
|
|
54
|
+
10. [AI agent / IDE integration](#ai-agent--ide-integration)
|
|
55
|
+
- [Claude Desktop](#claude-desktop)
|
|
56
|
+
- [Claude Code (CLI)](#claude-code-cli)
|
|
57
|
+
- [Cursor](#cursor)
|
|
58
|
+
- [Windsurf](#windsurf)
|
|
59
|
+
- [Cline (VS Code)](#cline-vs-code)
|
|
60
|
+
- [Continue (VS Code / JetBrains)](#continue-vs-code--jetbrains)
|
|
61
|
+
- [Zed](#zed)
|
|
62
|
+
- [Generic MCP client](#generic-mcp-client)
|
|
63
|
+
11. [Testing your setup](#testing-your-setup)
|
|
64
|
+
12. [Security notes](#security-notes)
|
|
65
|
+
13. [Troubleshooting](#troubleshooting)
|
|
66
|
+
14. [Development](#development)
|
|
67
|
+
15. [License](#license)
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## What it does
|
|
72
|
+
|
|
73
|
+
`mcp-secure-remote` spawns as a local stdio MCP server and forwards every
|
|
74
|
+
JSON-RPC message to a remote MCP server over HTTPS. Every outbound request
|
|
75
|
+
carries a client certificate you supply, so the remote server sees a
|
|
76
|
+
cryptographically authenticated connection — no OAuth dance, no bearer
|
|
77
|
+
tokens on the wire, no shared API keys.
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
┌──────────────┐ stdio ┌────────────────────┐ HTTPS + mTLS ┌───────────────┐
|
|
81
|
+
│ MCP client │───────────▶│ mcp-secure-remote │─────────────────▶│ Remote MCP │
|
|
82
|
+
│ (Claude, │ │ (uvx, this proxy) │ │ server │
|
|
83
|
+
│ Cursor, …) │◀───────────│ │◀─────────────────│ │
|
|
84
|
+
└──────────────┘ └────────────────────┘ └───────────────┘
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The remote MCP server can be implemented in any language — Python, Go, Rust,
|
|
88
|
+
Node.js, etc. The proxy only sees HTTPS + JSON-RPC.
|
|
89
|
+
|
|
90
|
+
## How it works
|
|
91
|
+
|
|
92
|
+
1. AI agent launches `mcp-secure-remote` (via `uvx`) as a local subprocess
|
|
93
|
+
and talks to it over stdio — the transport every MCP client already supports.
|
|
94
|
+
2. Proxy builds an `httpx` HTTPS client seeded with your client cert,
|
|
95
|
+
private key, and trusted CA bundle.
|
|
96
|
+
3. Proxy opens either a Streamable HTTP or SSE transport to the remote
|
|
97
|
+
server (configurable). TLS handshake presents the client cert; the server
|
|
98
|
+
validates it before forwarding the MCP session.
|
|
99
|
+
4. JSON-RPC frames flow bidirectionally. All proxy logging goes to stderr
|
|
100
|
+
so the stdio channel stays clean.
|
|
101
|
+
|
|
102
|
+
## Prerequisites
|
|
103
|
+
|
|
104
|
+
- Python **≥ 3.10**.
|
|
105
|
+
- [`uv`](https://docs.astral.sh/uv/getting-started/installation/) installed
|
|
106
|
+
(`curl -LsSf https://astral.sh/uv/install.sh | sh` on macOS/Linux).
|
|
107
|
+
- A client certificate + private key issued by a CA the remote MCP server
|
|
108
|
+
trusts (or a PKCS#12 bundle containing both).
|
|
109
|
+
- The CA bundle used by the remote server, if it is not in your OS trust
|
|
110
|
+
store (private/corporate CAs almost always need this).
|
|
111
|
+
- The remote MCP server URL (typically `https://host/mcp` or
|
|
112
|
+
`https://host/sse`).
|
|
113
|
+
|
|
114
|
+
## Install
|
|
115
|
+
|
|
116
|
+
`uvx` runs the package directly from PyPI in an isolated environment — no
|
|
117
|
+
explicit install step needed:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
uvx mcp-secure-remote --help
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
To install permanently in a `uv`-managed tool environment:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
uv tool install mcp-secure-remote
|
|
127
|
+
mcp-secure-remote --help
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Docker
|
|
131
|
+
|
|
132
|
+
Docker lets you run `mcp-secure-remote` without installing Python or `uv`.
|
|
133
|
+
The container reads stdio from its parent process, so MCP clients that
|
|
134
|
+
spawn subprocesses work exactly the same way — just replace `uvx` with
|
|
135
|
+
`docker run`.
|
|
136
|
+
|
|
137
|
+
### Dockerfile
|
|
138
|
+
|
|
139
|
+
```dockerfile
|
|
140
|
+
FROM python:3.12-slim
|
|
141
|
+
|
|
142
|
+
RUN pip install --no-cache-dir mcp-secure-remote
|
|
143
|
+
|
|
144
|
+
ENTRYPOINT ["mcp-secure-remote"]
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Build the image:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
docker build -t mcp-secure-remote .
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Run directly
|
|
154
|
+
|
|
155
|
+
Mount your cert directory (read-only) and pass the usual flags:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
docker run --rm -i \
|
|
159
|
+
-v /absolute/path/to/certs:/certs:ro \
|
|
160
|
+
mcp-secure-remote \
|
|
161
|
+
https://mcp.example.com/mcp \
|
|
162
|
+
--tls-cert /certs/client.crt \
|
|
163
|
+
--tls-key /certs/client.key \
|
|
164
|
+
--tls-ca /certs/ca-bundle.pem
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`-i` keeps stdin open — required because the proxy reads the MCP stream
|
|
168
|
+
from the container's stdin. `--rm` removes the container after it exits.
|
|
169
|
+
|
|
170
|
+
Using env vars to keep secrets out of command history:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
docker run --rm -i \
|
|
174
|
+
-v /absolute/path/to/certs:/certs:ro \
|
|
175
|
+
-e MCP_REMOTE_TLS_CERT=/certs/client.crt \
|
|
176
|
+
-e MCP_REMOTE_TLS_KEY=/certs/client.key \
|
|
177
|
+
-e MCP_REMOTE_TLS_CA=/certs/ca-bundle.pem \
|
|
178
|
+
mcp-secure-remote \
|
|
179
|
+
https://mcp.example.com/mcp
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### MCP client config
|
|
183
|
+
|
|
184
|
+
Replace `uvx` with `docker run` in any client config. Example for Claude
|
|
185
|
+
Desktop / Claude Code / Cursor:
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"mcpServers": {
|
|
190
|
+
"example": {
|
|
191
|
+
"command": "docker",
|
|
192
|
+
"args": [
|
|
193
|
+
"run", "--rm", "-i",
|
|
194
|
+
"-v", "/absolute/path/to/certs:/certs:ro",
|
|
195
|
+
"mcp-secure-remote",
|
|
196
|
+
"https://mcp.example.com/mcp",
|
|
197
|
+
"--tls-cert", "/certs/client.crt",
|
|
198
|
+
"--tls-key", "/certs/client.key",
|
|
199
|
+
"--tls-ca", "/certs/ca-bundle.pem"
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
To keep secrets out of the config, pass them via `-e` instead:
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"mcpServers": {
|
|
211
|
+
"example": {
|
|
212
|
+
"command": "docker",
|
|
213
|
+
"args": [
|
|
214
|
+
"run", "--rm", "-i",
|
|
215
|
+
"-v", "/absolute/path/to/certs:/certs:ro",
|
|
216
|
+
"-e", "MCP_REMOTE_TLS_CERT=/certs/client.crt",
|
|
217
|
+
"-e", "MCP_REMOTE_TLS_KEY=/certs/client.key",
|
|
218
|
+
"-e", "MCP_REMOTE_TLS_CA=/certs/ca-bundle.pem",
|
|
219
|
+
"mcp-secure-remote",
|
|
220
|
+
"https://mcp.example.com/mcp"
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Docker Compose
|
|
228
|
+
|
|
229
|
+
Useful when you want cert mounts and env vars declared once in version
|
|
230
|
+
control rather than repeated in every client config.
|
|
231
|
+
|
|
232
|
+
`compose.yml`:
|
|
233
|
+
|
|
234
|
+
```yaml
|
|
235
|
+
services:
|
|
236
|
+
mcp-proxy:
|
|
237
|
+
build: .
|
|
238
|
+
stdin_open: true
|
|
239
|
+
volumes:
|
|
240
|
+
- /absolute/path/to/certs:/certs:ro
|
|
241
|
+
environment:
|
|
242
|
+
MCP_REMOTE_TLS_CERT: /certs/client.crt
|
|
243
|
+
MCP_REMOTE_TLS_KEY: /certs/client.key
|
|
244
|
+
MCP_REMOTE_TLS_CA: /certs/ca-bundle.pem
|
|
245
|
+
command:
|
|
246
|
+
- https://mcp.example.com/mcp
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Run once to verify the connection:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
docker compose run --rm mcp-proxy
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Then point MCP clients at `docker compose run --rm mcp-proxy` as the
|
|
256
|
+
command (with no extra args — env and volume come from `compose.yml`):
|
|
257
|
+
|
|
258
|
+
```json
|
|
259
|
+
{
|
|
260
|
+
"mcpServers": {
|
|
261
|
+
"example": {
|
|
262
|
+
"command": "docker",
|
|
263
|
+
"args": ["compose", "run", "--rm", "mcp-proxy"]
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Generate or obtain client certificates
|
|
272
|
+
|
|
273
|
+
If your team already issues client certs, skip this section. For local
|
|
274
|
+
testing, generate a throw-away CA + client cert pair with OpenSSL:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
# CA
|
|
278
|
+
openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \
|
|
279
|
+
-keyout ca.key -out ca.crt -subj "/CN=dev-ca"
|
|
280
|
+
|
|
281
|
+
# client key + CSR
|
|
282
|
+
openssl req -newkey rsa:4096 -nodes \
|
|
283
|
+
-keyout client.key -out client.csr -subj "/CN=dev-client"
|
|
284
|
+
|
|
285
|
+
# sign client cert with CA
|
|
286
|
+
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
|
|
287
|
+
-out client.crt -days 365 -sha256
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Configure the remote MCP server to require client certs signed by `ca.crt`.
|
|
291
|
+
Point the proxy at `client.crt` + `client.key` + the server's CA bundle.
|
|
292
|
+
|
|
293
|
+
## Quick start
|
|
294
|
+
|
|
295
|
+
Cert + key pair:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
uvx mcp-secure-remote https://mcp.example.com/mcp \
|
|
299
|
+
--tls-cert ./certs/client.crt \
|
|
300
|
+
--tls-key ./certs/client.key \
|
|
301
|
+
--tls-ca ./certs/ca-bundle.pem
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
PKCS#12 bundle:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
uvx mcp-secure-remote https://mcp.example.com/mcp \
|
|
308
|
+
--tls-pfx ./certs/client.p12 \
|
|
309
|
+
--tls-passphrase "$P12_PASSPHRASE" \
|
|
310
|
+
--tls-ca ./certs/ca-bundle.pem
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Force SSE transport + pin minimum TLS:
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
uvx mcp-secure-remote https://mcp.example.com/sse \
|
|
317
|
+
--transport sse-only \
|
|
318
|
+
--tls-min-version TLSv1.3 \
|
|
319
|
+
--tls-cert ./certs/client.crt \
|
|
320
|
+
--tls-key ./certs/client.key \
|
|
321
|
+
--tls-ca ./certs/ca-bundle.pem
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## CLI parameters
|
|
325
|
+
|
|
326
|
+
Usage: `mcp-secure-remote <server-url> [options]`
|
|
327
|
+
|
|
328
|
+
`<server-url>` is a positional argument (required). Everything else is a
|
|
329
|
+
named flag.
|
|
330
|
+
|
|
331
|
+
### General
|
|
332
|
+
|
|
333
|
+
| Flag | Type | Default | Description |
|
|
334
|
+
| --- | --- | --- | --- |
|
|
335
|
+
| `<server-url>` | string (URL) | — | **Required.** Remote MCP endpoint. Must be `https://…` unless `--allow-http` is set. |
|
|
336
|
+
| `--header "Name: value"` | string (repeatable) | — | Extra HTTP header on every outbound request. Repeat the flag for multiple headers. |
|
|
337
|
+
| `--transport <strategy>` | enum | `http-first` | Transport negotiation. One of `http-first`, `sse-first`, `http-only`, `sse-only`. `-first` variants try the preferred transport then fall back; `-only` variants never fall back. |
|
|
338
|
+
| `--allow-http` | boolean | `false` | Permit plain `http://` URLs. Off by default; mTLS is meaningless over HTTP. |
|
|
339
|
+
| `--debug` | boolean | `false` | Verbose logging to stderr (parsed args, per-message trace, transport selection). |
|
|
340
|
+
| `--version` | boolean | — | Print version and exit. |
|
|
341
|
+
| `-h`, `--help` | boolean | — | Print usage and exit. |
|
|
342
|
+
|
|
343
|
+
### mTLS / TLS
|
|
344
|
+
|
|
345
|
+
| Flag | Type | Default | Description |
|
|
346
|
+
| --- | --- | --- | --- |
|
|
347
|
+
| `--tls-cert <path>` | path | — | PEM client certificate (leaf, optionally followed by chain intermediates). |
|
|
348
|
+
| `--tls-key <path>` | path | — | PEM private key matching `--tls-cert`. Must be supplied together with `--tls-cert`. |
|
|
349
|
+
| `--tls-ca <path>` | path | — | PEM CA bundle used to verify the remote server. Required for private CAs not in the OS trust store. |
|
|
350
|
+
| `--tls-pfx <path>` | path | — | PKCS#12 (`.pfx` / `.p12`) bundle. Mutually exclusive with `--tls-cert`/`--tls-key`. |
|
|
351
|
+
| `--tls-passphrase <value>` | string | — | Passphrase protecting the private key or PFX bundle. Prefer the env var to keep secrets off the command line. |
|
|
352
|
+
| `--tls-servername <name>` | string | URL hostname | SNI override. Use when the server cert's SAN differs from the URL host (e.g. IP literal, internal DNS). |
|
|
353
|
+
| `--tls-min-version <ver>` | enum | system default | Minimum TLS version: `TLSv1.2` or `TLSv1.3`. |
|
|
354
|
+
| `--tls-insecure-skip-verify`, `--tls-no-verify` | boolean | `false` | Disable server certificate validation. **Dev only.** Proxy prints a warning when enabled. |
|
|
355
|
+
|
|
356
|
+
### Parameter rules
|
|
357
|
+
|
|
358
|
+
- `--tls-cert` and `--tls-key` must appear together.
|
|
359
|
+
- `--tls-pfx` cannot combine with `--tls-cert`/`--tls-key`.
|
|
360
|
+
- `--allow-http` is required for any `http://` URL. Supplying mTLS flags
|
|
361
|
+
with `http://` triggers a warning (cert is not sent over plain HTTP).
|
|
362
|
+
- Unknown `--flags` cause parse failure with exit code 2.
|
|
363
|
+
- Argument errors exit with code 2; runtime errors exit with code 1.
|
|
364
|
+
|
|
365
|
+
## Environment variables
|
|
366
|
+
|
|
367
|
+
Every TLS flag has an env-var fallback so secrets can stay out of shell
|
|
368
|
+
history and MCP client configs.
|
|
369
|
+
|
|
370
|
+
| Variable | Equivalent flag | Values |
|
|
371
|
+
| --- | --- | --- |
|
|
372
|
+
| `MCP_REMOTE_TLS_CERT` | `--tls-cert` | path |
|
|
373
|
+
| `MCP_REMOTE_TLS_KEY` | `--tls-key` | path |
|
|
374
|
+
| `MCP_REMOTE_TLS_CA` | `--tls-ca` | path |
|
|
375
|
+
| `MCP_REMOTE_TLS_PFX` | `--tls-pfx` | path |
|
|
376
|
+
| `MCP_REMOTE_TLS_PASSPHRASE` | `--tls-passphrase` | string |
|
|
377
|
+
| `MCP_REMOTE_TLS_SERVERNAME` | `--tls-servername` | string |
|
|
378
|
+
| `MCP_REMOTE_TLS_MIN_VERSION` | `--tls-min-version` | `TLSv1.2` \| `TLSv1.3` |
|
|
379
|
+
| `MCP_REMOTE_TLS_INSECURE` | `--tls-insecure-skip-verify` | `1` / `true` / `yes` to disable verify |
|
|
380
|
+
|
|
381
|
+
Precedence: explicit CLI flag overrides env var.
|
|
382
|
+
|
|
383
|
+
## AI agent / IDE integration
|
|
384
|
+
|
|
385
|
+
Use **absolute paths** for all cert files — agents do not inherit your
|
|
386
|
+
shell's working directory.
|
|
387
|
+
|
|
388
|
+
### Claude Desktop
|
|
389
|
+
|
|
390
|
+
File: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
391
|
+
(macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows).
|
|
392
|
+
|
|
393
|
+
```json
|
|
394
|
+
{
|
|
395
|
+
"mcpServers": {
|
|
396
|
+
"example": {
|
|
397
|
+
"command": "uvx",
|
|
398
|
+
"args": [
|
|
399
|
+
"mcp-secure-remote",
|
|
400
|
+
"https://mcp.example.com/mcp",
|
|
401
|
+
"--tls-cert", "/absolute/path/client.crt",
|
|
402
|
+
"--tls-key", "/absolute/path/client.key",
|
|
403
|
+
"--tls-ca", "/absolute/path/ca-bundle.pem"
|
|
404
|
+
]
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Using env vars to keep secrets out of the config file:
|
|
411
|
+
|
|
412
|
+
```json
|
|
413
|
+
{
|
|
414
|
+
"mcpServers": {
|
|
415
|
+
"example": {
|
|
416
|
+
"command": "uvx",
|
|
417
|
+
"args": ["mcp-secure-remote", "https://mcp.example.com/mcp"],
|
|
418
|
+
"env": {
|
|
419
|
+
"MCP_REMOTE_TLS_CERT": "/absolute/path/client.crt",
|
|
420
|
+
"MCP_REMOTE_TLS_KEY": "/absolute/path/client.key",
|
|
421
|
+
"MCP_REMOTE_TLS_CA": "/absolute/path/ca-bundle.pem"
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Restart Claude Desktop after editing.
|
|
429
|
+
|
|
430
|
+
### Claude Code (CLI)
|
|
431
|
+
|
|
432
|
+
Add via `claude mcp add` or edit `~/.claude.json` / project `.mcp.json`:
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
claude mcp add example uvx -- mcp-secure-remote \
|
|
436
|
+
https://mcp.example.com/mcp \
|
|
437
|
+
--tls-cert /absolute/path/client.crt \
|
|
438
|
+
--tls-key /absolute/path/client.key \
|
|
439
|
+
--tls-ca /absolute/path/ca-bundle.pem
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Or in `.mcp.json`:
|
|
443
|
+
|
|
444
|
+
```json
|
|
445
|
+
{
|
|
446
|
+
"mcpServers": {
|
|
447
|
+
"example": {
|
|
448
|
+
"command": "uvx",
|
|
449
|
+
"args": [
|
|
450
|
+
"mcp-secure-remote",
|
|
451
|
+
"https://mcp.example.com/mcp",
|
|
452
|
+
"--tls-cert", "/absolute/path/client.crt",
|
|
453
|
+
"--tls-key", "/absolute/path/client.key",
|
|
454
|
+
"--tls-ca", "/absolute/path/ca-bundle.pem"
|
|
455
|
+
]
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Cursor
|
|
462
|
+
|
|
463
|
+
File: `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per project).
|
|
464
|
+
|
|
465
|
+
```json
|
|
466
|
+
{
|
|
467
|
+
"mcpServers": {
|
|
468
|
+
"example": {
|
|
469
|
+
"command": "uvx",
|
|
470
|
+
"args": [
|
|
471
|
+
"mcp-secure-remote",
|
|
472
|
+
"https://mcp.example.com/mcp",
|
|
473
|
+
"--tls-cert", "/absolute/path/client.crt",
|
|
474
|
+
"--tls-key", "/absolute/path/client.key",
|
|
475
|
+
"--tls-ca", "/absolute/path/ca-bundle.pem"
|
|
476
|
+
],
|
|
477
|
+
"env": {
|
|
478
|
+
"MCP_REMOTE_TLS_PASSPHRASE": "…optional…"
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Windsurf
|
|
486
|
+
|
|
487
|
+
File: `~/.codeium/windsurf/mcp_config.json`.
|
|
488
|
+
|
|
489
|
+
```json
|
|
490
|
+
{
|
|
491
|
+
"mcpServers": {
|
|
492
|
+
"example": {
|
|
493
|
+
"command": "uvx",
|
|
494
|
+
"args": [
|
|
495
|
+
"mcp-secure-remote",
|
|
496
|
+
"https://mcp.example.com/mcp",
|
|
497
|
+
"--tls-cert", "/absolute/path/client.crt",
|
|
498
|
+
"--tls-key", "/absolute/path/client.key",
|
|
499
|
+
"--tls-ca", "/absolute/path/ca-bundle.pem"
|
|
500
|
+
]
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Cline (VS Code)
|
|
507
|
+
|
|
508
|
+
Open the Cline MCP panel → "Configure MCP Servers" or edit
|
|
509
|
+
`cline_mcp_settings.json` from its extension storage directly:
|
|
510
|
+
|
|
511
|
+
```json
|
|
512
|
+
{
|
|
513
|
+
"mcpServers": {
|
|
514
|
+
"example": {
|
|
515
|
+
"command": "uvx",
|
|
516
|
+
"args": [
|
|
517
|
+
"mcp-secure-remote",
|
|
518
|
+
"https://mcp.example.com/mcp",
|
|
519
|
+
"--tls-cert", "/absolute/path/client.crt",
|
|
520
|
+
"--tls-key", "/absolute/path/client.key",
|
|
521
|
+
"--tls-ca", "/absolute/path/ca-bundle.pem"
|
|
522
|
+
],
|
|
523
|
+
"disabled": false,
|
|
524
|
+
"autoApprove": []
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Continue (VS Code / JetBrains)
|
|
531
|
+
|
|
532
|
+
File: `~/.continue/config.json` (or `config.yaml`).
|
|
533
|
+
|
|
534
|
+
```json
|
|
535
|
+
{
|
|
536
|
+
"experimental": {
|
|
537
|
+
"modelContextProtocolServers": [
|
|
538
|
+
{
|
|
539
|
+
"transport": {
|
|
540
|
+
"type": "stdio",
|
|
541
|
+
"command": "uvx",
|
|
542
|
+
"args": [
|
|
543
|
+
"mcp-secure-remote",
|
|
544
|
+
"https://mcp.example.com/mcp",
|
|
545
|
+
"--tls-cert", "/absolute/path/client.crt",
|
|
546
|
+
"--tls-key", "/absolute/path/client.key",
|
|
547
|
+
"--tls-ca", "/absolute/path/ca-bundle.pem"
|
|
548
|
+
]
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
]
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### Zed
|
|
557
|
+
|
|
558
|
+
File: `~/.config/zed/settings.json`.
|
|
559
|
+
|
|
560
|
+
```json
|
|
561
|
+
{
|
|
562
|
+
"context_servers": {
|
|
563
|
+
"example": {
|
|
564
|
+
"command": {
|
|
565
|
+
"path": "uvx",
|
|
566
|
+
"args": [
|
|
567
|
+
"mcp-secure-remote",
|
|
568
|
+
"https://mcp.example.com/mcp",
|
|
569
|
+
"--tls-cert", "/absolute/path/client.crt",
|
|
570
|
+
"--tls-key", "/absolute/path/client.key",
|
|
571
|
+
"--tls-ca", "/absolute/path/ca-bundle.pem"
|
|
572
|
+
]
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Generic MCP client
|
|
580
|
+
|
|
581
|
+
Any client that spawns stdio MCP servers works:
|
|
582
|
+
|
|
583
|
+
- `command`: `uvx`
|
|
584
|
+
- `args`: `["mcp-secure-remote", "<server-url>", …tls-flags]`
|
|
585
|
+
- Optional `env` block for `MCP_REMOTE_TLS_*` variables to keep secrets
|
|
586
|
+
out of the args array.
|
|
587
|
+
|
|
588
|
+
## Testing your setup
|
|
589
|
+
|
|
590
|
+
The bundled `mcp-secure-remote-client` verifies the TLS handshake and
|
|
591
|
+
enumerates the server's capabilities — no real agent needed:
|
|
592
|
+
|
|
593
|
+
```bash
|
|
594
|
+
uvx mcp-secure-remote-client https://mcp.example.com/mcp \
|
|
595
|
+
--tls-cert ./certs/client.crt \
|
|
596
|
+
--tls-key ./certs/client.key \
|
|
597
|
+
--tls-ca ./certs/ca-bundle.pem
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
Output: negotiated capabilities + lists of tools, resources, prompts.
|
|
601
|
+
|
|
602
|
+
Add `--debug` for per-message tracing.
|
|
603
|
+
|
|
604
|
+
For a fully local endpoint that actually requires client-certificate
|
|
605
|
+
authentication, see [LOCAL_MTLS_TESTING.md](./LOCAL_MTLS_TESTING.md).
|
|
606
|
+
|
|
607
|
+
## Security notes
|
|
608
|
+
|
|
609
|
+
- **HTTPS only by default.** `http://` URLs are refused unless
|
|
610
|
+
`--allow-http` is explicitly set. Proxy additionally warns when mTLS
|
|
611
|
+
flags are combined with `http://` because the client cert will not be
|
|
612
|
+
sent.
|
|
613
|
+
- **Skip-verify prints a warning.** `--tls-insecure-skip-verify` disables
|
|
614
|
+
server certificate validation; intended for local dev loops only.
|
|
615
|
+
- **Prefer env vars for passphrases.** Anything on the CLI may leak into
|
|
616
|
+
process listings, shell history, or agent logs.
|
|
617
|
+
- **Proxy logs to stderr.** stdout is reserved for the MCP JSON-RPC stream.
|
|
618
|
+
- **No credential persistence.** Proxy does not write certs, keys, or
|
|
619
|
+
tokens to disk.
|
|
620
|
+
- **Pin TLS 1.3** (`--tls-min-version TLSv1.3`) when the server supports
|
|
621
|
+
it, to avoid downgrade-prone 1.2 cipher suites.
|
|
622
|
+
|
|
623
|
+
## Troubleshooting
|
|
624
|
+
|
|
625
|
+
**`CERTIFICATE_VERIFY_FAILED` / `unable to verify the first certificate`**
|
|
626
|
+
Point `--tls-ca` at the PEM bundle that signed the remote server's cert.
|
|
627
|
+
OS trust store alone is not enough for private CAs.
|
|
628
|
+
|
|
629
|
+
**`Hostname/IP does not match certificate's altnames`**
|
|
630
|
+
Set `--tls-servername` to the SAN the server cert presents.
|
|
631
|
+
|
|
632
|
+
**Private key malformed or passphrase error**
|
|
633
|
+
Ensure the key file is PEM-encoded. If encrypted, supply
|
|
634
|
+
`--tls-passphrase` (or `MCP_REMOTE_TLS_PASSPHRASE`).
|
|
635
|
+
|
|
636
|
+
**TLS handshake failure / `alert bad certificate`**
|
|
637
|
+
Server rejected your client cert. Check:
|
|
638
|
+
- Cert signed by a CA the server trusts.
|
|
639
|
+
- Key matches cert:
|
|
640
|
+
`openssl x509 -noout -modulus -in client.crt | openssl md5`
|
|
641
|
+
vs. `openssl rsa -noout -modulus -in client.key | openssl md5`.
|
|
642
|
+
- Intermediate chain present in `--tls-cert`.
|
|
643
|
+
|
|
644
|
+
**Agent shows "failed to start server" with no detail.**
|
|
645
|
+
Run the exact same command in a terminal to see stderr. Agents hide
|
|
646
|
+
subprocess stderr by default.
|
|
647
|
+
|
|
648
|
+
**Remote transport hangs.**
|
|
649
|
+
Try `--transport sse-only` or `--transport http-only` to isolate which
|
|
650
|
+
transport the server actually implements. Add `--debug`.
|
|
651
|
+
|
|
652
|
+
## Development
|
|
653
|
+
|
|
654
|
+
```bash
|
|
655
|
+
# clone and set up dev environment
|
|
656
|
+
git clone https://github.com/framedparadox/mcp-secure-remote.git
|
|
657
|
+
cd mcp-secure-remote
|
|
658
|
+
uv sync
|
|
659
|
+
|
|
660
|
+
# run directly from source
|
|
661
|
+
uv run mcp-secure-remote --help
|
|
662
|
+
uv run mcp-secure-remote-client --help
|
|
663
|
+
|
|
664
|
+
# typecheck
|
|
665
|
+
uv run mypy src/
|
|
666
|
+
|
|
667
|
+
# build wheel + sdist
|
|
668
|
+
uv build
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
## License
|
|
672
|
+
|
|
673
|
+
MIT — see [LICENSE](./LICENSE).
|