mcpower-proxy 0.0.67__tar.gz → 0.0.69__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.
Potentially problematic release.
This version of mcpower-proxy might be problematic. Click here for more details.
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/PKG-INFO +2 -2
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/pyproject.toml +2 -2
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/mcpower_proxy.egg-info/PKG-INFO +2 -2
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/mcpower_proxy.egg-info/requires.txt +1 -1
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/apis/security_policy.py +4 -2
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/utils/cli.py +2 -4
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/wrapper/__version__.py +1 -1
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/wrapper/middleware.py +76 -36
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/wrapper/server.py +19 -11
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/LICENSE +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/README.md +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/setup.cfg +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/LICENSE +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/main.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/mcpower_proxy.egg-info/SOURCES.txt +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/mcpower_proxy.egg-info/dependency_links.txt +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/mcpower_proxy.egg-info/entry_points.txt +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/mcpower_proxy.egg-info/top_level.txt +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/__init__.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/apis/__init__.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/logs/__init__.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/logs/audit_trail.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/logs/logger.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/redaction/__init__.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/redaction/constants.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/redaction/gitleaks_rules.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/redaction/pii_rules.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/redaction/redactor.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/ui/__init__.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/ui/classes.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/ui/confirmation.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/ui/simple_dialog.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/ui/xdialog/__init__.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/ui/xdialog/constants.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/ui/xdialog/mac_dialogs.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/ui/xdialog/tk_dialogs.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/ui/xdialog/windows_custom_dialog.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/ui/xdialog/windows_dialogs.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/ui/xdialog/windows_structs.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/ui/xdialog/yad_dialogs.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/ui/xdialog/zenity_dialogs.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/utils/__init__.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/utils/config.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/utils/copy.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/utils/ids.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/utils/json.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/utils/mcp_configs.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/wrapper/__init__.py +0 -0
- {mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/wrapper/schema.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcpower-proxy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.69
|
|
4
4
|
Summary: MCPower Security proxy
|
|
5
5
|
Author-email: MCPower Security <support@mcpower.tech>
|
|
6
6
|
License: Apache License
|
|
@@ -209,7 +209,7 @@ Keywords: mcp,security,proxy,monitoring,audit,redaction,policy-enforcement
|
|
|
209
209
|
Requires-Python: ~=3.11.0
|
|
210
210
|
Description-Content-Type: text/markdown
|
|
211
211
|
License-File: LICENSE
|
|
212
|
-
Requires-Dist: fastmcp==2.13.0.
|
|
212
|
+
Requires-Dist: fastmcp==2.13.0.2
|
|
213
213
|
Requires-Dist: httpx>=0.25.0
|
|
214
214
|
Requires-Dist: mcp>=1.0.0
|
|
215
215
|
Requires-Dist: watchdog>=3.0.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mcpower-proxy"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.69"
|
|
4
4
|
description = "MCPower Security proxy"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = "~=3.11.0"
|
|
@@ -9,7 +9,7 @@ authors = [
|
|
|
9
9
|
{ name = "MCPower Security", email = "support@mcpower.tech" }
|
|
10
10
|
]
|
|
11
11
|
dependencies = [
|
|
12
|
-
"fastmcp==2.13.0.
|
|
12
|
+
"fastmcp==2.13.0.2",
|
|
13
13
|
"httpx>=0.25.0",
|
|
14
14
|
"mcp>=1.0.0",
|
|
15
15
|
"watchdog>=3.0.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcpower-proxy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.69
|
|
4
4
|
Summary: MCPower Security proxy
|
|
5
5
|
Author-email: MCPower Security <support@mcpower.tech>
|
|
6
6
|
License: Apache License
|
|
@@ -209,7 +209,7 @@ Keywords: mcp,security,proxy,monitoring,audit,redaction,policy-enforcement
|
|
|
209
209
|
Requires-Python: ~=3.11.0
|
|
210
210
|
Description-Content-Type: text/markdown
|
|
211
211
|
License-File: LICENSE
|
|
212
|
-
Requires-Dist: fastmcp==2.13.0.
|
|
212
|
+
Requires-Dist: fastmcp==2.13.0.2
|
|
213
213
|
Requires-Dist: httpx>=0.25.0
|
|
214
214
|
Requires-Dist: mcp>=1.0.0
|
|
215
215
|
Requires-Dist: watchdog>=3.0.0
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Security Policy API Client"""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import time
|
|
4
5
|
import uuid
|
|
5
6
|
from typing import Dict, Any, Optional, List
|
|
6
|
-
import time
|
|
7
7
|
|
|
8
8
|
import httpx
|
|
9
9
|
|
|
@@ -13,6 +13,7 @@ from modules.logs.logger import MCPLogger
|
|
|
13
13
|
from modules.redaction import redact
|
|
14
14
|
from modules.utils.config import get_api_url, get_user_id
|
|
15
15
|
from modules.utils.json import safe_json_dumps, to_dict
|
|
16
|
+
from wrapper.__version__ import __version__
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class SecurityAPIError(Exception):
|
|
@@ -166,6 +167,7 @@ class SecurityPolicyClient:
|
|
|
166
167
|
|
|
167
168
|
headers = {
|
|
168
169
|
"Content-Type": "application/json",
|
|
170
|
+
"User-Agent": f"MCPower-{__version__}",
|
|
169
171
|
"X-User-UID": self.user_id,
|
|
170
172
|
"X-App-UID": self.app_id
|
|
171
173
|
}
|
|
@@ -188,7 +190,7 @@ class SecurityPolicyClient:
|
|
|
188
190
|
raise SecurityAPIError(f"Unsupported HTTP method: {method}. Supported methods: POST, PUT")
|
|
189
191
|
|
|
190
192
|
on_make_request_duration = time.time() - on_make_request_start_time
|
|
191
|
-
self.logger.
|
|
193
|
+
self.logger.debug(f"PROFILE: {method} id: {id} make_request duration: {on_make_request_duration:.2f} seconds url: {url}")
|
|
192
194
|
|
|
193
195
|
match response.status_code:
|
|
194
196
|
case 200:
|
|
@@ -7,7 +7,7 @@ import argparse
|
|
|
7
7
|
def parse_args():
|
|
8
8
|
"""Parse command line arguments"""
|
|
9
9
|
parser = argparse.ArgumentParser(
|
|
10
|
-
description="
|
|
10
|
+
description="Transparent MCP wrapper with security middleware for real-time policy enforcement and monitoring.",
|
|
11
11
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
12
12
|
epilog="""
|
|
13
13
|
Examples:
|
|
@@ -24,10 +24,8 @@ Examples:
|
|
|
24
24
|
%(prog)s --wrapped-config '{"command": "node", "args": ["server.js"]}' --name MyWrapper
|
|
25
25
|
|
|
26
26
|
Reference Links:
|
|
27
|
-
•
|
|
28
|
-
• FastMCP Middleware: https://gofastmcp.com/servers/middleware
|
|
27
|
+
• MCPower Proxy: https://github.com/ai-mcpower/mcpower-proxy
|
|
29
28
|
• MCP Official: https://modelcontextprotocol.io
|
|
30
|
-
• Claude MCP Config: https://docs.anthropic.com/en/docs/claude-code/mcp
|
|
31
29
|
"""
|
|
32
30
|
)
|
|
33
31
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
FastMCP middleware for security policy enforcement
|
|
3
3
|
Implements pre/post interception for all MCP operations
|
|
4
4
|
"""
|
|
5
|
+
import asyncio
|
|
5
6
|
import sys
|
|
6
7
|
import time
|
|
7
8
|
import urllib.parse
|
|
@@ -12,6 +13,12 @@ from typing import Any, Dict, List, Optional
|
|
|
12
13
|
from fastmcp.exceptions import FastMCPError
|
|
13
14
|
from fastmcp.server.middleware.middleware import Middleware, MiddlewareContext, CallNext
|
|
14
15
|
from fastmcp.server.proxy import ProxyClient
|
|
16
|
+
from httpx import HTTPStatusError
|
|
17
|
+
from mcp import ErrorData
|
|
18
|
+
|
|
19
|
+
from mcpower_shared.mcp_types import (create_policy_request, create_policy_response, AgentContext, EnvironmentContext,
|
|
20
|
+
InitRequest,
|
|
21
|
+
ServerRef, ToolRef, UserConfirmation)
|
|
15
22
|
from modules.apis.security_policy import SecurityPolicyClient
|
|
16
23
|
from modules.logs.audit_trail import AuditTrailLogger
|
|
17
24
|
from modules.logs.logger import MCPLogger
|
|
@@ -24,10 +31,6 @@ from modules.utils.json import safe_json_dumps, to_dict
|
|
|
24
31
|
from modules.utils.mcp_configs import extract_wrapped_server_info
|
|
25
32
|
from wrapper.schema import merge_input_schema_with_existing
|
|
26
33
|
|
|
27
|
-
from mcpower_shared.mcp_types import (create_policy_request, create_policy_response, AgentContext, EnvironmentContext,
|
|
28
|
-
InitRequest,
|
|
29
|
-
ServerRef, ToolRef, UserConfirmation)
|
|
30
|
-
|
|
31
34
|
|
|
32
35
|
class MockContext:
|
|
33
36
|
"""Mock context for internal operations"""
|
|
@@ -53,10 +56,7 @@ class MockContext:
|
|
|
53
56
|
class SecurityMiddleware(Middleware):
|
|
54
57
|
"""FastMCP middleware for security policy enforcement"""
|
|
55
58
|
|
|
56
|
-
app_id: str = ""
|
|
57
59
|
_TOOLS_INIT_DEBOUNCE_SECONDS = 60
|
|
58
|
-
_last_tools_init_time: Optional[float] = None
|
|
59
|
-
_last_workspace_root: Optional[str] = None
|
|
60
60
|
|
|
61
61
|
def __init__(self,
|
|
62
62
|
wrapped_server_configs: dict,
|
|
@@ -72,6 +72,9 @@ class SecurityMiddleware(Middleware):
|
|
|
72
72
|
self.audit_logger = audit_logger
|
|
73
73
|
self.app_id = ""
|
|
74
74
|
self._last_workspace_root = None
|
|
75
|
+
self._last_tools_init_time: Optional[float] = None
|
|
76
|
+
self._tools_list_in_progress: Optional[asyncio.Task] = None
|
|
77
|
+
self._tools_list_lock = asyncio.Lock()
|
|
75
78
|
|
|
76
79
|
self.wrapped_server_name, self.wrapped_server_transport = (
|
|
77
80
|
extract_wrapped_server_info(self.wrapper_server_name, self.logger, self.wrapped_server_configs)
|
|
@@ -88,17 +91,36 @@ class SecurityMiddleware(Middleware):
|
|
|
88
91
|
async def on_message(self, context: MiddlewareContext, call_next: CallNext) -> Any:
|
|
89
92
|
self.logger.info(f"on_message: {redact(safe_json_dumps(context))}")
|
|
90
93
|
|
|
91
|
-
#
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
self.
|
|
94
|
+
# Skip workspace check for `initialize` calls to avoid premature app_uid changes.
|
|
95
|
+
# The `initialize` request doesn't contain workspace data, so checking it would
|
|
96
|
+
# cause unnecessary audit log flushes before the actual workspace init arrives.
|
|
97
|
+
if context.method != "initialize":
|
|
98
|
+
# Check workspace roots and re-initialize app_uid if workspace changed
|
|
99
|
+
workspace_roots = await self._extract_workspace_roots(context)
|
|
100
|
+
current_workspace_root = workspace_roots[0] if workspace_roots else str(Path.home() / ".mcpower")
|
|
101
|
+
if current_workspace_root != self._last_workspace_root:
|
|
102
|
+
self.logger.debug(
|
|
103
|
+
f"Workspace root changed from {self._last_workspace_root} to {current_workspace_root}")
|
|
104
|
+
self._last_workspace_root = current_workspace_root
|
|
105
|
+
self.app_id = read_app_uid(logger=self.logger, project_folder_path=current_workspace_root)
|
|
106
|
+
self.audit_logger.set_app_uid(self.app_id)
|
|
99
107
|
|
|
100
108
|
operation_type = "message"
|
|
101
|
-
|
|
109
|
+
|
|
110
|
+
async def call_next_wrapper(ctx):
|
|
111
|
+
try:
|
|
112
|
+
return await call_next(ctx)
|
|
113
|
+
except HTTPStatusError as e:
|
|
114
|
+
if e.response.status_code in (401, 403):
|
|
115
|
+
raise FastMCPError(ErrorData(
|
|
116
|
+
code=-32000,
|
|
117
|
+
message="Authentication required",
|
|
118
|
+
data={
|
|
119
|
+
"type": "unauthorized",
|
|
120
|
+
"details": "Please provide valid authentication credentials"
|
|
121
|
+
}
|
|
122
|
+
))
|
|
123
|
+
raise e
|
|
102
124
|
|
|
103
125
|
match context.type:
|
|
104
126
|
case "request":
|
|
@@ -115,13 +137,13 @@ class SecurityMiddleware(Middleware):
|
|
|
115
137
|
operation_type = "prompt"
|
|
116
138
|
case "tools/list":
|
|
117
139
|
# Special handling for tools/list - call /init instead of normal inspection
|
|
118
|
-
return await self._handle_tools_list(context,
|
|
140
|
+
return await self._handle_tools_list(context, call_next_wrapper)
|
|
119
141
|
case "initialize" | "resources/list" | "resources/templates/list" | "prompts/list":
|
|
120
|
-
return await
|
|
142
|
+
return await call_next_wrapper(context)
|
|
121
143
|
|
|
122
144
|
return await self._handle_operation(
|
|
123
145
|
context=context,
|
|
124
|
-
call_next=
|
|
146
|
+
call_next=call_next_wrapper,
|
|
125
147
|
error_class=FastMCPError,
|
|
126
148
|
operation_type=operation_type
|
|
127
149
|
)
|
|
@@ -181,15 +203,15 @@ class SecurityMiddleware(Middleware):
|
|
|
181
203
|
return await ProxyClient.default_progress_handler(progress, total, message)
|
|
182
204
|
|
|
183
205
|
async def secure_log_handler(self, log_message):
|
|
184
|
-
# FIXME: log_message should be redacted before logging,
|
|
206
|
+
# FIXME: log_message should be redacted before logging,
|
|
185
207
|
self.logger.info(f"secure_log_handler: {str(log_message)[:100]}...")
|
|
186
208
|
# FIXME: log_message should be reviewed with policy before forwarding
|
|
187
|
-
|
|
209
|
+
|
|
188
210
|
# Handle case where log_message.data is a string instead of dict
|
|
189
211
|
# The default_log_handler expects data to be a dict with 'msg' and 'extra' keys
|
|
190
212
|
if hasattr(log_message, 'data') and isinstance(log_message.data, str):
|
|
191
213
|
log_message = safe_copy(log_message, {'data': {'msg': log_message.data, 'extra': None}})
|
|
192
|
-
|
|
214
|
+
|
|
193
215
|
return await ProxyClient.default_log_handler(log_message)
|
|
194
216
|
|
|
195
217
|
async def _handle_operation(self, context: MiddlewareContext, call_next, error_class, operation_type: str):
|
|
@@ -222,7 +244,8 @@ class SecurityMiddleware(Middleware):
|
|
|
222
244
|
prompt_id=prompt_id
|
|
223
245
|
)
|
|
224
246
|
on_inspect_request_duration = time.time() - on_inspect_request_start_time
|
|
225
|
-
self.logger.
|
|
247
|
+
self.logger.debug(
|
|
248
|
+
f"PROFILE: {operation_type} id: {event_id} inspect_request duration: {on_inspect_request_duration:.2f} seconds")
|
|
226
249
|
|
|
227
250
|
await self._enforce_decision(
|
|
228
251
|
decision=request_decision,
|
|
@@ -251,7 +274,8 @@ class SecurityMiddleware(Middleware):
|
|
|
251
274
|
# Call wrapped MCP with cleaned context (e.g., no wrapper args)
|
|
252
275
|
result = await call_next(cleaned_context)
|
|
253
276
|
on_call_next_duration = time.time() - on_call_next_start_time
|
|
254
|
-
self.logger.
|
|
277
|
+
self.logger.debug(
|
|
278
|
+
f"PROFILE: {operation_type} id: {event_id} call_next duration: {on_call_next_duration:.2f} seconds")
|
|
255
279
|
|
|
256
280
|
response_content = self._extract_response_content(result)
|
|
257
281
|
|
|
@@ -276,7 +300,8 @@ class SecurityMiddleware(Middleware):
|
|
|
276
300
|
prompt_id=prompt_id
|
|
277
301
|
)
|
|
278
302
|
on_inspect_response_duration = time.time() - on_inspect_response_start_time
|
|
279
|
-
self.logger.
|
|
303
|
+
self.logger.debug(
|
|
304
|
+
f"PROFILE: {operation_type} id: {event_id} inspect_response duration: {on_inspect_response_duration:.2f} seconds")
|
|
280
305
|
|
|
281
306
|
await self._enforce_decision(
|
|
282
307
|
decision=response_decision,
|
|
@@ -301,15 +326,30 @@ class SecurityMiddleware(Middleware):
|
|
|
301
326
|
prompt_id=prompt_id
|
|
302
327
|
)
|
|
303
328
|
on_handle_operation_duration = time.time() - on_handle_operation_start_time
|
|
304
|
-
self.logger.
|
|
329
|
+
self.logger.debug(
|
|
330
|
+
f"PROFILE: {operation_type} id: {event_id} duration: {on_handle_operation_duration:.2f} seconds")
|
|
305
331
|
return result
|
|
306
332
|
|
|
307
333
|
async def _handle_tools_list(self, context: MiddlewareContext, call_next: CallNext) -> Any:
|
|
308
|
-
"""Handle tools/list by calling /init API and modifying schemas"""
|
|
334
|
+
"""Handle tools/list by calling /init API and modifying schemas with deduplication"""
|
|
309
335
|
event_id = generate_event_id()
|
|
310
336
|
on_handle_tools_list_start_time = time.time()
|
|
311
|
-
|
|
312
|
-
|
|
337
|
+
|
|
338
|
+
async with self._tools_list_lock:
|
|
339
|
+
if not self._tools_list_in_progress or self._tools_list_in_progress.done():
|
|
340
|
+
self._tools_list_in_progress = asyncio.create_task(call_next(context))
|
|
341
|
+
shared_task = self._tools_list_in_progress
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
result = await shared_task
|
|
345
|
+
except Exception as e:
|
|
346
|
+
async with self._tools_list_lock:
|
|
347
|
+
if self._tools_list_in_progress is shared_task:
|
|
348
|
+
self._tools_list_in_progress = None
|
|
349
|
+
raise
|
|
350
|
+
self.logger.debug(
|
|
351
|
+
f"PROFILE: tools/list call_next duration: {time.time() - on_handle_tools_list_start_time:.2f} seconds id: {event_id}")
|
|
352
|
+
|
|
313
353
|
tools_list = None
|
|
314
354
|
if isinstance(result, list):
|
|
315
355
|
tools_list = result
|
|
@@ -339,11 +379,13 @@ class SecurityMiddleware(Middleware):
|
|
|
339
379
|
enhanced_result = result
|
|
340
380
|
|
|
341
381
|
on_handle_tools_list_duration = time.time() - on_handle_tools_list_start_time
|
|
342
|
-
self.logger.
|
|
382
|
+
self.logger.debug(
|
|
383
|
+
f"PROFILE: tools/list enhanced_result duration: {on_handle_tools_list_duration:.2f} seconds id: {event_id}")
|
|
343
384
|
return enhanced_result
|
|
344
385
|
|
|
345
386
|
on_handle_tools_list_duration = time.time() - on_handle_tools_list_start_time
|
|
346
|
-
self.logger.
|
|
387
|
+
self.logger.debug(
|
|
388
|
+
f"PROFILE: tools/list result duration: {on_handle_tools_list_duration:.2f} seconds id: {event_id}")
|
|
347
389
|
|
|
348
390
|
return result
|
|
349
391
|
|
|
@@ -482,12 +524,12 @@ class SecurityMiddleware(Middleware):
|
|
|
482
524
|
file_path_prefix = 'file://'
|
|
483
525
|
if uri.startswith(file_path_prefix):
|
|
484
526
|
path = urllib.parse.unquote(uri[len(file_path_prefix):])
|
|
485
|
-
|
|
527
|
+
|
|
486
528
|
# Windows fix: remove leading slash before drive letter
|
|
487
529
|
# file:///C:/path becomes /C:/path, should be C:/path
|
|
488
530
|
if sys.platform == 'win32' and len(path) >= 3 and path[0] == '/' and path[2] == ':':
|
|
489
531
|
path = path[1:]
|
|
490
|
-
|
|
532
|
+
|
|
491
533
|
try:
|
|
492
534
|
resolved_path = str(Path(path).resolve())
|
|
493
535
|
workspace_roots.append(resolved_path)
|
|
@@ -614,7 +656,6 @@ class SecurityMiddleware(Middleware):
|
|
|
614
656
|
# Don't fail the operation if API call fails - just log the error
|
|
615
657
|
self.logger.error(f"Failed to record user confirmation: {e}")
|
|
616
658
|
|
|
617
|
-
|
|
618
659
|
@staticmethod
|
|
619
660
|
def _create_security_api_failure_decision(error: Exception) -> Dict[str, Any]:
|
|
620
661
|
"""Create a standard failure decision when security API is unavailable/failing/unreachable"""
|
|
@@ -722,7 +763,7 @@ class SecurityMiddleware(Middleware):
|
|
|
722
763
|
error_parts = [
|
|
723
764
|
f"SECURITY POLICY NEEDS MORE INFORMATION FOR REVIEWING {stage_title}:",
|
|
724
765
|
'\n'.join(reasons),
|
|
725
|
-
''
|
|
766
|
+
'' # newline
|
|
726
767
|
]
|
|
727
768
|
|
|
728
769
|
if need_fields:
|
|
@@ -749,7 +790,6 @@ class SecurityMiddleware(Middleware):
|
|
|
749
790
|
error_parts.append("MISSING INFORMATION:")
|
|
750
791
|
error_parts.extend(need_fields)
|
|
751
792
|
|
|
752
|
-
|
|
753
793
|
error_parts.append("\nMANDATORY ACTIONS:")
|
|
754
794
|
error_parts.append("1. Add/Edit ALL affected fields according to the required information")
|
|
755
795
|
error_parts.append("2. Retry the tool call")
|
|
@@ -6,10 +6,11 @@ Implements transparent 1:1 MCP proxying with security middleware
|
|
|
6
6
|
import logging
|
|
7
7
|
|
|
8
8
|
from fastmcp.server.middleware.logging import StructuredLoggingMiddleware
|
|
9
|
-
from fastmcp.server.proxy import ProxyClient, default_proxy_roots_handler, FastMCPProxy
|
|
9
|
+
from fastmcp.server.proxy import ProxyClient, default_proxy_roots_handler, FastMCPProxy, StatefulProxyClient
|
|
10
10
|
|
|
11
11
|
from modules.logs.audit_trail import AuditTrailLogger
|
|
12
12
|
from modules.logs.logger import MCPLogger
|
|
13
|
+
from modules.utils.json import safe_json_dumps
|
|
13
14
|
from .__version__ import __version__
|
|
14
15
|
from .middleware import SecurityMiddleware
|
|
15
16
|
|
|
@@ -42,7 +43,7 @@ def create_wrapper_server(wrapper_server_name: str,
|
|
|
42
43
|
logger=logger,
|
|
43
44
|
audit_logger=audit_logger
|
|
44
45
|
)
|
|
45
|
-
|
|
46
|
+
|
|
46
47
|
# Log MCPower startup to audit trail
|
|
47
48
|
audit_logger.log_event("mcpower_start", {
|
|
48
49
|
"wrapper_version": __version__,
|
|
@@ -51,16 +52,23 @@ def create_wrapper_server(wrapper_server_name: str,
|
|
|
51
52
|
})
|
|
52
53
|
|
|
53
54
|
# Create FastMCP server as proxy with our security-aware ProxyClient
|
|
55
|
+
# Use StatefulProxyClient for remote servers (mcp-remote or url-based transports)
|
|
56
|
+
config_str = safe_json_dumps(wrapped_server_configs)
|
|
57
|
+
is_remote = '"mcp-remote",' in config_str or '"url":' in config_str
|
|
58
|
+
backend_class = StatefulProxyClient if is_remote else ProxyClient
|
|
59
|
+
backend = backend_class(
|
|
60
|
+
wrapped_server_configs,
|
|
61
|
+
name=wrapper_server_name,
|
|
62
|
+
roots=default_proxy_roots_handler, # Use default for filesystem roots
|
|
63
|
+
sampling_handler=security_middleware.secure_sampling_handler,
|
|
64
|
+
elicitation_handler=security_middleware.secure_elicitation_handler,
|
|
65
|
+
log_handler=security_middleware.secure_log_handler,
|
|
66
|
+
progress_handler=security_middleware.secure_progress_handler,
|
|
67
|
+
)
|
|
68
|
+
|
|
54
69
|
def client_factory():
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
name=wrapper_server_name,
|
|
58
|
-
roots=default_proxy_roots_handler, # Use default for filesystem roots
|
|
59
|
-
sampling_handler=security_middleware.secure_sampling_handler,
|
|
60
|
-
elicitation_handler=security_middleware.secure_elicitation_handler,
|
|
61
|
-
log_handler=security_middleware.secure_log_handler,
|
|
62
|
-
progress_handler=security_middleware.secure_progress_handler,
|
|
63
|
-
)
|
|
70
|
+
# we must return the same instance, otherwise StatefulProxyClient doesn't play nice with mcp-remote
|
|
71
|
+
return backend
|
|
64
72
|
|
|
65
73
|
server = FastMCPProxy(client_factory=client_factory, name=wrapper_server_name, version=__version__)
|
|
66
74
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/mcpower_proxy.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcpower_proxy-0.0.67 → mcpower_proxy-0.0.69}/src/modules/ui/xdialog/windows_custom_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|