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.
@@ -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).