mcp-proxy-adapter 6.7.0__py3-none-any.whl → 6.7.1__py3-none-any.whl
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_proxy_adapter/core/mtls_proxy.py +181 -0
- mcp_proxy_adapter/main.py +50 -5
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.7.0.dist-info → mcp_proxy_adapter-6.7.1.dist-info}/METADATA +1 -1
- {mcp_proxy_adapter-6.7.0.dist-info → mcp_proxy_adapter-6.7.1.dist-info}/RECORD +8 -7
- {mcp_proxy_adapter-6.7.0.dist-info → mcp_proxy_adapter-6.7.1.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.7.0.dist-info → mcp_proxy_adapter-6.7.1.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.7.0.dist-info → mcp_proxy_adapter-6.7.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,181 @@
|
|
1
|
+
"""
|
2
|
+
mTLS Proxy for MCP Proxy Adapter
|
3
|
+
|
4
|
+
This module provides mTLS proxy functionality that accepts mTLS connections
|
5
|
+
and proxies them to the internal hypercorn server.
|
6
|
+
|
7
|
+
Author: Vasiliy Zdanovskiy
|
8
|
+
email: vasilyvz@gmail.com
|
9
|
+
"""
|
10
|
+
|
11
|
+
import asyncio
|
12
|
+
import ssl
|
13
|
+
import logging
|
14
|
+
from typing import Optional, Dict, Any
|
15
|
+
from pathlib import Path
|
16
|
+
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
|
20
|
+
class MTLSProxy:
|
21
|
+
"""
|
22
|
+
mTLS Proxy that accepts mTLS connections and proxies them to internal server.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(self,
|
26
|
+
external_host: str,
|
27
|
+
external_port: int,
|
28
|
+
internal_host: str = "127.0.0.1",
|
29
|
+
internal_port: int = 9000,
|
30
|
+
cert_file: Optional[str] = None,
|
31
|
+
key_file: Optional[str] = None,
|
32
|
+
ca_cert: Optional[str] = None):
|
33
|
+
"""
|
34
|
+
Initialize mTLS Proxy.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
external_host: External host to bind to
|
38
|
+
external_port: External port to bind to
|
39
|
+
internal_host: Internal server host
|
40
|
+
internal_port: Internal server port
|
41
|
+
cert_file: Server certificate file
|
42
|
+
key_file: Server private key file
|
43
|
+
ca_cert: CA certificate file for client verification
|
44
|
+
"""
|
45
|
+
self.external_host = external_host
|
46
|
+
self.external_port = external_port
|
47
|
+
self.internal_host = internal_host
|
48
|
+
self.internal_port = internal_port
|
49
|
+
self.cert_file = cert_file
|
50
|
+
self.key_file = key_file
|
51
|
+
self.ca_cert = ca_cert
|
52
|
+
self.server = None
|
53
|
+
|
54
|
+
async def start(self):
|
55
|
+
"""Start the mTLS proxy server."""
|
56
|
+
try:
|
57
|
+
# Create SSL context
|
58
|
+
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
59
|
+
ssl_context.load_cert_chain(self.cert_file, self.key_file)
|
60
|
+
|
61
|
+
if self.ca_cert:
|
62
|
+
ssl_context.load_verify_locations(self.ca_cert)
|
63
|
+
ssl_context.verify_mode = ssl.CERT_REQUIRED
|
64
|
+
else:
|
65
|
+
ssl_context.verify_mode = ssl.CERT_NONE
|
66
|
+
|
67
|
+
# Start server
|
68
|
+
self.server = await asyncio.start_server(
|
69
|
+
self._handle_client,
|
70
|
+
self.external_host,
|
71
|
+
self.external_port,
|
72
|
+
ssl=ssl_context
|
73
|
+
)
|
74
|
+
|
75
|
+
logger.info(f"🔐 mTLS Proxy started on {self.external_host}:{self.external_port}")
|
76
|
+
logger.info(f"🌐 Proxying to {self.internal_host}:{self.internal_port}")
|
77
|
+
|
78
|
+
except Exception as e:
|
79
|
+
logger.error(f"❌ Failed to start mTLS proxy: {e}")
|
80
|
+
raise
|
81
|
+
|
82
|
+
async def stop(self):
|
83
|
+
"""Stop the mTLS proxy server."""
|
84
|
+
if self.server:
|
85
|
+
self.server.close()
|
86
|
+
await self.server.wait_closed()
|
87
|
+
logger.info("🔐 mTLS Proxy stopped")
|
88
|
+
|
89
|
+
async def _handle_client(self, reader, writer):
|
90
|
+
"""Handle client connection."""
|
91
|
+
try:
|
92
|
+
# Get client address
|
93
|
+
client_addr = writer.get_extra_info('peername')
|
94
|
+
logger.info(f"🔐 mTLS connection from {client_addr}")
|
95
|
+
|
96
|
+
# Connect to internal server
|
97
|
+
internal_reader, internal_writer = await asyncio.open_connection(
|
98
|
+
self.internal_host, self.internal_port
|
99
|
+
)
|
100
|
+
|
101
|
+
# Create bidirectional proxy
|
102
|
+
await asyncio.gather(
|
103
|
+
self._proxy_data(reader, internal_writer, "client->server"),
|
104
|
+
self._proxy_data(internal_reader, writer, "server->client")
|
105
|
+
)
|
106
|
+
|
107
|
+
except Exception as e:
|
108
|
+
logger.error(f"❌ Error handling client connection: {e}")
|
109
|
+
finally:
|
110
|
+
try:
|
111
|
+
writer.close()
|
112
|
+
await writer.wait_closed()
|
113
|
+
except:
|
114
|
+
pass
|
115
|
+
|
116
|
+
async def _proxy_data(self, reader, writer, direction):
|
117
|
+
"""Proxy data between reader and writer."""
|
118
|
+
try:
|
119
|
+
while True:
|
120
|
+
data = await reader.read(4096)
|
121
|
+
if not data:
|
122
|
+
break
|
123
|
+
writer.write(data)
|
124
|
+
await writer.drain()
|
125
|
+
except Exception as e:
|
126
|
+
logger.debug(f"Proxy connection closed ({direction}): {e}")
|
127
|
+
finally:
|
128
|
+
try:
|
129
|
+
writer.close()
|
130
|
+
await writer.wait_closed()
|
131
|
+
except:
|
132
|
+
pass
|
133
|
+
|
134
|
+
|
135
|
+
async def start_mtls_proxy(config: Dict[str, Any]) -> Optional[MTLSProxy]:
|
136
|
+
"""
|
137
|
+
Start mTLS proxy based on configuration.
|
138
|
+
|
139
|
+
Args:
|
140
|
+
config: Application configuration
|
141
|
+
|
142
|
+
Returns:
|
143
|
+
MTLSProxy instance if started, None otherwise
|
144
|
+
"""
|
145
|
+
# Check if mTLS is enabled
|
146
|
+
protocol = config.get("server", {}).get("protocol", "http")
|
147
|
+
verify_client = config.get("transport", {}).get("verify_client", False)
|
148
|
+
|
149
|
+
# Only start mTLS proxy if mTLS is explicitly enabled
|
150
|
+
if protocol != "mtls" and not verify_client:
|
151
|
+
logger.info("🌐 Regular mode: no mTLS proxy needed")
|
152
|
+
return None
|
153
|
+
|
154
|
+
# Get configuration
|
155
|
+
server_config = config.get("server", {})
|
156
|
+
transport_config = config.get("transport", {})
|
157
|
+
|
158
|
+
external_host = server_config.get("host", "0.0.0.0")
|
159
|
+
external_port = server_config.get("port", 8000)
|
160
|
+
internal_port = external_port + 1000 # Internal port
|
161
|
+
|
162
|
+
cert_file = transport_config.get("cert_file")
|
163
|
+
key_file = transport_config.get("key_file")
|
164
|
+
ca_cert = transport_config.get("ca_cert")
|
165
|
+
|
166
|
+
if not cert_file or not key_file:
|
167
|
+
logger.warning("⚠️ mTLS enabled but certificates not configured")
|
168
|
+
return None
|
169
|
+
|
170
|
+
# Create and start proxy
|
171
|
+
proxy = MTLSProxy(
|
172
|
+
external_host=external_host,
|
173
|
+
external_port=external_port,
|
174
|
+
internal_port=internal_port,
|
175
|
+
cert_file=cert_file,
|
176
|
+
key_file=key_file,
|
177
|
+
ca_cert=ca_cert
|
178
|
+
)
|
179
|
+
|
180
|
+
await proxy.start()
|
181
|
+
return proxy
|
mcp_proxy_adapter/main.py
CHANGED
@@ -89,8 +89,23 @@ def main():
|
|
89
89
|
verify_client = config.get("transport.verify_client", False)
|
90
90
|
chk_hostname = config.get("transport.chk_hostname", False)
|
91
91
|
|
92
|
-
#
|
93
|
-
|
92
|
+
# Check if mTLS is required
|
93
|
+
is_mtls_mode = protocol == "mtls" or verify_client
|
94
|
+
|
95
|
+
if is_mtls_mode:
|
96
|
+
# mTLS mode: hypercorn on localhost, mTLS proxy on external port
|
97
|
+
hypercorn_host = "127.0.0.1" # localhost only
|
98
|
+
hypercorn_port = port + 1000 # internal port
|
99
|
+
mtls_proxy_port = port # external port
|
100
|
+
ssl_enabled = True
|
101
|
+
print(f"🔐 mTLS Mode: hypercorn on {hypercorn_host}:{hypercorn_port}, mTLS proxy on {host}:{mtls_proxy_port}")
|
102
|
+
else:
|
103
|
+
# Regular mode: hypercorn on external port (no proxy needed)
|
104
|
+
hypercorn_host = host
|
105
|
+
hypercorn_port = port
|
106
|
+
mtls_proxy_port = None
|
107
|
+
ssl_enabled = protocol == "https"
|
108
|
+
print(f"🌐 Regular Mode: hypercorn on {hypercorn_host}:{hypercorn_port}")
|
94
109
|
|
95
110
|
# SSL configuration based on protocol
|
96
111
|
ssl_cert_file = None
|
@@ -118,7 +133,11 @@ def main():
|
|
118
133
|
print("🔍 Source: configuration")
|
119
134
|
|
120
135
|
print("🚀 Starting MCP Proxy Adapter")
|
121
|
-
|
136
|
+
if mtls_proxy_port:
|
137
|
+
print(f"🔐 mTLS Proxy: {host}:{mtls_proxy_port}")
|
138
|
+
print(f"🌐 Internal Server: {hypercorn_host}:{hypercorn_port}")
|
139
|
+
else:
|
140
|
+
print(f"🌐 Server: {hypercorn_host}:{hypercorn_port}")
|
122
141
|
print(f"🔒 Protocol: {protocol}")
|
123
142
|
if ssl_enabled:
|
124
143
|
print("🔐 SSL: Enabled")
|
@@ -131,7 +150,7 @@ def main():
|
|
131
150
|
|
132
151
|
# Configure hypercorn using framework
|
133
152
|
config_hypercorn = hypercorn.config.Config()
|
134
|
-
config_hypercorn.bind = [f"{
|
153
|
+
config_hypercorn.bind = [f"{hypercorn_host}:{hypercorn_port}"]
|
135
154
|
|
136
155
|
if ssl_enabled and ssl_cert_file and ssl_key_file:
|
137
156
|
# Use framework to convert SSL configuration
|
@@ -201,7 +220,33 @@ def main():
|
|
201
220
|
|
202
221
|
# Run the server
|
203
222
|
try:
|
204
|
-
|
223
|
+
if is_mtls_mode:
|
224
|
+
# mTLS mode: start hypercorn and mTLS proxy
|
225
|
+
print("🔐 Starting mTLS mode with proxy...")
|
226
|
+
|
227
|
+
async def run_mtls_mode():
|
228
|
+
# Start hypercorn server on localhost
|
229
|
+
hypercorn_task = asyncio.create_task(
|
230
|
+
hypercorn.asyncio.serve(app, config_hypercorn)
|
231
|
+
)
|
232
|
+
|
233
|
+
# Start mTLS proxy on external port
|
234
|
+
from mcp_proxy_adapter.core.mtls_proxy import start_mtls_proxy
|
235
|
+
proxy = await start_mtls_proxy(config.get_all())
|
236
|
+
|
237
|
+
if proxy:
|
238
|
+
print("✅ mTLS proxy started successfully")
|
239
|
+
else:
|
240
|
+
print("⚠️ mTLS proxy not started, running hypercorn only")
|
241
|
+
|
242
|
+
# Wait for hypercorn
|
243
|
+
await hypercorn_task
|
244
|
+
|
245
|
+
asyncio.run(run_mtls_mode())
|
246
|
+
else:
|
247
|
+
# Regular mode: start hypercorn only (no proxy needed)
|
248
|
+
print("🌐 Starting regular mode...")
|
249
|
+
asyncio.run(hypercorn.asyncio.serve(app, config_hypercorn))
|
205
250
|
except KeyboardInterrupt:
|
206
251
|
print("\n🛑 Server stopped by user (Ctrl+C)")
|
207
252
|
if is_shutdown_requested():
|
mcp_proxy_adapter/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcp-proxy-adapter
|
3
|
-
Version: 6.7.
|
3
|
+
Version: 6.7.1
|
4
4
|
Summary: Powerful JSON-RPC microservices framework with built-in security, authentication, and proxy registration
|
5
5
|
Home-page: https://github.com/maverikod/mcp-proxy-adapter
|
6
6
|
Author: Vasiliy Zdanovskiy
|
@@ -2,9 +2,9 @@ mcp_proxy_adapter/__init__.py,sha256=iH0EBBsRj_cfZJpAIsgN_8tTdfefhnl6uUKHjLHhWDQ
|
|
2
2
|
mcp_proxy_adapter/__main__.py,sha256=sq3tANRuTd18euamt0Bmn1sJeAyzXENZ5VvsMwbrDFA,579
|
3
3
|
mcp_proxy_adapter/config.py,sha256=QpoPaUKcWJ-eu6HYphhIZmkc2M-p1JgpLFAgolf_l5s,20161
|
4
4
|
mcp_proxy_adapter/custom_openapi.py,sha256=XRviX-C-ZkSKdBhORhDTdeN_1FWyEfXZADiASft3t9I,28149
|
5
|
-
mcp_proxy_adapter/main.py,sha256=
|
5
|
+
mcp_proxy_adapter/main.py,sha256=ILdGeikcZls2y9Uro0bQLi53FPSuJv_yZBio-3WD2zM,9233
|
6
6
|
mcp_proxy_adapter/openapi.py,sha256=2UZOI09ZDRJuBYBjKbMyb2U4uASszoCMD5o_4ktRpvg,13480
|
7
|
-
mcp_proxy_adapter/version.py,sha256=
|
7
|
+
mcp_proxy_adapter/version.py,sha256=f7P2CcobyBU9CJovfKbOnqeKTQDfipyBZBpKBXU94nM,74
|
8
8
|
mcp_proxy_adapter/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
mcp_proxy_adapter/api/app.py,sha256=GGhA5X7HQPLJFxeaWHu5YUwi2ldPvPvJD2YcaL9Nbo0,37874
|
10
10
|
mcp_proxy_adapter/api/handlers.py,sha256=X-hcMNVeTAu4yVkKJEChEsj2bFptUS6sLNN-Wysjkow,10011
|
@@ -69,6 +69,7 @@ mcp_proxy_adapter/core/errors.py,sha256=UNEfdmK0zPGJrWH1zUMRjHIJMcoVDcBO4w8xxKHB
|
|
69
69
|
mcp_proxy_adapter/core/logging.py,sha256=gNI6vfPQC7jrUtVu6NeDsmU72JPlrRRBhtJipL1eVrI,9560
|
70
70
|
mcp_proxy_adapter/core/mtls_asgi.py,sha256=tvk0P9024s18dcCHY9AaQIecT4ojOTv21EuQWXwooU0,5200
|
71
71
|
mcp_proxy_adapter/core/mtls_asgi_app.py,sha256=DT_fTUH1RkvBa3ThbyCyNb-XUHyCb4DqaKA1gcZC6z4,6538
|
72
|
+
mcp_proxy_adapter/core/mtls_proxy.py,sha256=5APlWs0ImiHGEC65W_7F-PbVO3NZ2BVSj9r14AcUtTE,6011
|
72
73
|
mcp_proxy_adapter/core/mtls_server.py,sha256=_hj6QWuExKX2LRohYvjPGFC2qTutS7ObegpEc09QijM,10117
|
73
74
|
mcp_proxy_adapter/core/protocol_manager.py,sha256=iaXWsfm1XSfemz5QQBesMluc4cwf-LtuZVi9bm1uj28,14680
|
74
75
|
mcp_proxy_adapter/core/proxy_client.py,sha256=CB6KBhV3vH2GU5nZ27VZ_xlNbYTAU_tnYFrkuK5He58,6094
|
@@ -133,8 +134,8 @@ mcp_proxy_adapter/schemas/base_schema.json,sha256=v9G9cGMd4dRhCZsOQ_FMqOi5VFyVbI
|
|
133
134
|
mcp_proxy_adapter/schemas/openapi_schema.json,sha256=C3yLkwmDsvnLW9B5gnKKdBGl4zxkeU-rEmjTrNVsQU0,8405
|
134
135
|
mcp_proxy_adapter/schemas/roles.json,sha256=pgf_ZyqKyXbfGUxvobpiLiSJz9zzxrMuoVWEkEpz3N8,764
|
135
136
|
mcp_proxy_adapter/schemas/roles_schema.json,sha256=deHgI7L6GwfBXacOlNtDgDJelDThppClC3Ti4Eh8rJY,5659
|
136
|
-
mcp_proxy_adapter-6.7.
|
137
|
-
mcp_proxy_adapter-6.7.
|
138
|
-
mcp_proxy_adapter-6.7.
|
139
|
-
mcp_proxy_adapter-6.7.
|
140
|
-
mcp_proxy_adapter-6.7.
|
137
|
+
mcp_proxy_adapter-6.7.1.dist-info/METADATA,sha256=82G1E37shptreMST_9zGo9VaqSyFShYmAiOUSvI9x2o,8510
|
138
|
+
mcp_proxy_adapter-6.7.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
139
|
+
mcp_proxy_adapter-6.7.1.dist-info/entry_points.txt,sha256=Bf-O5Aq80n22Ayu9fI9BgidzWqwzIVaqextAddTuHZw,563
|
140
|
+
mcp_proxy_adapter-6.7.1.dist-info/top_level.txt,sha256=JZT7vPLBYrtroX-ij68JBhJYbjDdghcV-DFySRy-Nnw,18
|
141
|
+
mcp_proxy_adapter-6.7.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|