mcpower-proxy 0.0.61__tar.gz → 0.0.72__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.
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/PKG-INFO +3 -3
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/pyproject.toml +4 -4
- mcpower_proxy-0.0.72/src/ide_tools/__init__.py +12 -0
- mcpower_proxy-0.0.72/src/ide_tools/cursor/__init__.py +11 -0
- mcpower_proxy-0.0.72/src/ide_tools/cursor/constants.py +58 -0
- mcpower_proxy-0.0.72/src/ide_tools/cursor/format.py +35 -0
- mcpower_proxy-0.0.72/src/ide_tools/cursor/router.py +100 -0
- mcpower_proxy-0.0.72/src/ide_tools/router.py +45 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/main.py +11 -4
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/mcpower_proxy.egg-info/PKG-INFO +3 -3
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/mcpower_proxy.egg-info/SOURCES.txt +7 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/mcpower_proxy.egg-info/requires.txt +2 -2
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/mcpower_proxy.egg-info/top_level.txt +1 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/apis/security_policy.py +11 -6
- mcpower_proxy-0.0.72/src/modules/decision_handler.py +219 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/logs/audit_trail.py +22 -17
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/logs/logger.py +14 -18
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/redaction/redactor.py +112 -107
- mcpower_proxy-0.0.72/src/modules/ui/__init__.py +1 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/ui/confirmation.py +0 -1
- mcpower_proxy-0.0.72/src/modules/utils/cli.py +76 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/utils/ids.py +55 -10
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/utils/json.py +3 -3
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/wrapper/__version__.py +1 -1
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/wrapper/middleware.py +121 -210
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/wrapper/server.py +19 -11
- mcpower_proxy-0.0.61/src/modules/ui/__init__.py +0 -1
- mcpower_proxy-0.0.61/src/modules/utils/cli.py +0 -46
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/LICENSE +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/README.md +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/setup.cfg +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/LICENSE +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/mcpower_proxy.egg-info/dependency_links.txt +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/mcpower_proxy.egg-info/entry_points.txt +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/__init__.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/apis/__init__.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/logs/__init__.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/redaction/__init__.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/redaction/constants.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/redaction/gitleaks_rules.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/redaction/pii_rules.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/ui/classes.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/ui/simple_dialog.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/ui/xdialog/__init__.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/ui/xdialog/constants.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/ui/xdialog/mac_dialogs.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/ui/xdialog/tk_dialogs.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/ui/xdialog/windows_custom_dialog.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/ui/xdialog/windows_dialogs.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/ui/xdialog/windows_structs.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/ui/xdialog/yad_dialogs.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/ui/xdialog/zenity_dialogs.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/utils/__init__.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/utils/config.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/utils/copy.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/modules/utils/mcp_configs.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/src/wrapper/__init__.py +0 -0
- {mcpower_proxy-0.0.61 → mcpower_proxy-0.0.72}/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.72
|
|
4
4
|
Summary: MCPower Security proxy
|
|
5
5
|
Author-email: MCPower Security <support@mcpower.tech>
|
|
6
6
|
License: Apache License
|
|
@@ -209,14 +209,14 @@ 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
|
|
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
|
|
216
216
|
Requires-Dist: jsonc-parser>=1.1.5
|
|
217
217
|
Requires-Dist: jsonpath-ng>=1.7.0
|
|
218
218
|
Requires-Dist: pydantic>=2.8.0
|
|
219
|
-
Requires-Dist: mcpower-shared==0.1.
|
|
219
|
+
Requires-Dist: mcpower-shared==0.1.3
|
|
220
220
|
Dynamic: license-file
|
|
221
221
|
|
|
222
222
|
# MCPower Proxy
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mcpower-proxy"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.72"
|
|
4
4
|
description = "MCPower Security proxy"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = "~=3.11.0"
|
|
@@ -9,14 +9,14 @@ authors = [
|
|
|
9
9
|
{ name = "MCPower Security", email = "support@mcpower.tech" }
|
|
10
10
|
]
|
|
11
11
|
dependencies = [
|
|
12
|
-
"fastmcp
|
|
12
|
+
"fastmcp==2.13.0.2",
|
|
13
13
|
"httpx>=0.25.0",
|
|
14
14
|
"mcp>=1.0.0",
|
|
15
15
|
"watchdog>=3.0.0",
|
|
16
16
|
"jsonc-parser>=1.1.5",
|
|
17
17
|
"jsonpath-ng>=1.7.0",
|
|
18
18
|
"pydantic>=2.8.0",
|
|
19
|
-
"mcpower-shared==0.1.
|
|
19
|
+
"mcpower-shared==0.1.3",
|
|
20
20
|
]
|
|
21
21
|
|
|
22
22
|
[project.license]
|
|
@@ -31,7 +31,7 @@ build-backend = "setuptools.build_meta"
|
|
|
31
31
|
|
|
32
32
|
[tool.setuptools]
|
|
33
33
|
package-dir = {"" = "src"}
|
|
34
|
-
packages = ["modules", "modules.apis", "modules.logs", "modules.redaction", "modules.ui", "modules.ui.xdialog", "modules.utils", "wrapper"]
|
|
34
|
+
packages = ["modules", "modules.apis", "modules.logs", "modules.redaction", "modules.ui", "modules.ui.xdialog", "modules.utils", "wrapper", "ide_tools", "ide_tools.cursor"]
|
|
35
35
|
py-modules = ["main"]
|
|
36
36
|
|
|
37
37
|
[tool.uv]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IDE Tools - IDE Safety Validation Module
|
|
3
|
+
|
|
4
|
+
Provides validation and user confirmation for AI-generated operations in IDEs.
|
|
5
|
+
Supports multiple IDEs with per-IDE handlers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .router import main as router_main
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"router_main",
|
|
12
|
+
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cursor Hook Constants
|
|
3
|
+
|
|
4
|
+
Configuration values specific to Cursor hook handlers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from enum import Enum
|
|
8
|
+
|
|
9
|
+
from ide_tools.common.hooks.types import HookConfig, OutputFormat
|
|
10
|
+
from ide_tools.cursor.format import cursor_output_formatter
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HookPermission(str, Enum):
|
|
14
|
+
"""Cursor hook response permission values"""
|
|
15
|
+
ALLOW = "allow"
|
|
16
|
+
DENY = "deny"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Cursor-specific configuration
|
|
20
|
+
CURSOR_CONFIG = HookConfig(
|
|
21
|
+
output_format=OutputFormat(
|
|
22
|
+
allow_exit_code=0,
|
|
23
|
+
deny_exit_code=1,
|
|
24
|
+
error_exit_code=1,
|
|
25
|
+
formatter=cursor_output_formatter
|
|
26
|
+
),
|
|
27
|
+
server_name="mcpower_cursor",
|
|
28
|
+
client_name="cursor",
|
|
29
|
+
max_content_length=100000
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Hook descriptions from https://cursor.com/docs/agent/hooks#hook-events
|
|
33
|
+
CURSOR_HOOKS = {
|
|
34
|
+
"beforeShellExecution": {
|
|
35
|
+
"name": "beforeShellExecution",
|
|
36
|
+
"description": "Triggered before a shell command is executed by the agent. "
|
|
37
|
+
"Allows inspection and potential blocking of shell commands.",
|
|
38
|
+
"version": "1.0.0"
|
|
39
|
+
},
|
|
40
|
+
"afterShellExecution": {
|
|
41
|
+
"name": "afterShellExecution",
|
|
42
|
+
"description": "Triggered after a shell command completes execution. "
|
|
43
|
+
"Provides access to command output and exit status.",
|
|
44
|
+
"version": "1.0.0"
|
|
45
|
+
},
|
|
46
|
+
"beforeReadFile": {
|
|
47
|
+
"name": "beforeReadFile",
|
|
48
|
+
"description": "Triggered before the agent reads a file. "
|
|
49
|
+
"Allows inspection and potential blocking of file read operations.",
|
|
50
|
+
"version": "1.0.0"
|
|
51
|
+
},
|
|
52
|
+
"beforeSubmitPrompt": {
|
|
53
|
+
"name": "beforeSubmitPrompt",
|
|
54
|
+
"description": "Triggered before a prompt is submitted to the AI model. "
|
|
55
|
+
"Allows inspection and modification of prompts.",
|
|
56
|
+
"version": "1.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cursor-specific output formatting
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def cursor_output_formatter(hook_type: str, allowed: bool, user_msg: Optional[str], agent_msg: Optional[str]) -> str:
|
|
10
|
+
"""
|
|
11
|
+
Format output for Cursor IDE
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
hook_type: "permission" or "continue"
|
|
15
|
+
allowed: True for allow/continue, False for deny/block
|
|
16
|
+
user_msg: Message for user
|
|
17
|
+
agent_msg: Message for agent/logs
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
JSON string in Cursor format
|
|
21
|
+
"""
|
|
22
|
+
if hook_type == "permission":
|
|
23
|
+
result = {"permission": "allow" if allowed else "deny"}
|
|
24
|
+
if user_msg:
|
|
25
|
+
result["user_message"] = user_msg
|
|
26
|
+
if agent_msg:
|
|
27
|
+
result["agent_message"] = agent_msg
|
|
28
|
+
else: # continue
|
|
29
|
+
result = {"continue": allowed}
|
|
30
|
+
if user_msg:
|
|
31
|
+
result["user_message"] = user_msg
|
|
32
|
+
if agent_msg:
|
|
33
|
+
result["agent_message"] = agent_msg
|
|
34
|
+
|
|
35
|
+
return json.dumps(result)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cursor Router
|
|
3
|
+
|
|
4
|
+
Routes Cursor hook calls to appropriate handlers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
import uuid
|
|
11
|
+
|
|
12
|
+
from ide_tools.common.hooks.init import handle_init
|
|
13
|
+
from ide_tools.common.hooks.prompt_submit import handle_prompt_submit
|
|
14
|
+
from ide_tools.common.hooks.read_file import handle_read_file
|
|
15
|
+
from ide_tools.common.hooks.shell_execution import handle_shell_execution
|
|
16
|
+
from modules.logs.audit_trail import AuditTrailLogger
|
|
17
|
+
from modules.logs.logger import MCPLogger
|
|
18
|
+
from .constants import CURSOR_HOOKS, CURSOR_CONFIG
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def route_cursor_hook(logger: MCPLogger, audit_logger: AuditTrailLogger, stdin_input: str):
|
|
22
|
+
"""
|
|
23
|
+
Route Cursor hook to appropriate shared handler
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
logger: MCPLogger instance
|
|
27
|
+
audit_logger: AuditTrailLogger instance
|
|
28
|
+
stdin_input: Raw input string from stdin
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
input_data = json.loads(stdin_input)
|
|
32
|
+
|
|
33
|
+
hook_event_name = input_data.get("hook_event_name")
|
|
34
|
+
if not hook_event_name:
|
|
35
|
+
logger.error("Missing required field 'hook_event_name' in input")
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
|
|
38
|
+
conversation_id = input_data.get("conversation_id")
|
|
39
|
+
if not conversation_id:
|
|
40
|
+
logger.error("Missing required field 'conversation_id' in input")
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
|
|
43
|
+
generation_id = input_data.get("generation_id")
|
|
44
|
+
if not generation_id:
|
|
45
|
+
logger.error("Missing required field 'generation_id' in input")
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
|
|
48
|
+
workspace_roots = input_data.get("workspace_roots")
|
|
49
|
+
if workspace_roots is None:
|
|
50
|
+
logger.error("Missing required field 'workspace_roots' in input")
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
if not isinstance(workspace_roots, list):
|
|
54
|
+
logger.error("Invalid 'workspace_roots': must be a list")
|
|
55
|
+
sys.exit(1)
|
|
56
|
+
|
|
57
|
+
prompt_id = conversation_id[:8]
|
|
58
|
+
event_id = uuid.uuid4().hex[:8]
|
|
59
|
+
cwd = workspace_roots[0] if workspace_roots else None
|
|
60
|
+
|
|
61
|
+
logger.info(
|
|
62
|
+
f"Cursor router: routing to {hook_event_name} handler "
|
|
63
|
+
f"(prompt_id={prompt_id}, event_id={event_id}, cwd={cwd})")
|
|
64
|
+
|
|
65
|
+
# Route to appropriate handler
|
|
66
|
+
if hook_event_name == "init":
|
|
67
|
+
asyncio.run(handle_init(
|
|
68
|
+
logger=logger,
|
|
69
|
+
audit_logger=audit_logger,
|
|
70
|
+
event_id=event_id,
|
|
71
|
+
cwd=cwd,
|
|
72
|
+
server_name=CURSOR_CONFIG.server_name,
|
|
73
|
+
client_name="cursor",
|
|
74
|
+
hooks=CURSOR_HOOKS
|
|
75
|
+
))
|
|
76
|
+
elif hook_event_name == "beforeShellExecution":
|
|
77
|
+
asyncio.run(
|
|
78
|
+
handle_shell_execution(logger, audit_logger, stdin_input, prompt_id, event_id, cwd, CURSOR_CONFIG,
|
|
79
|
+
hook_event_name, is_request=True))
|
|
80
|
+
elif hook_event_name == "afterShellExecution":
|
|
81
|
+
asyncio.run(
|
|
82
|
+
handle_shell_execution(logger, audit_logger, stdin_input, prompt_id, event_id, cwd, CURSOR_CONFIG,
|
|
83
|
+
hook_event_name, is_request=False))
|
|
84
|
+
elif hook_event_name == "beforeReadFile":
|
|
85
|
+
asyncio.run(handle_read_file(logger, audit_logger, stdin_input, prompt_id, event_id, cwd, CURSOR_CONFIG,
|
|
86
|
+
hook_event_name))
|
|
87
|
+
elif hook_event_name == "beforeSubmitPrompt":
|
|
88
|
+
asyncio.run(
|
|
89
|
+
handle_prompt_submit(logger, audit_logger, stdin_input, prompt_id, event_id, cwd, CURSOR_CONFIG,
|
|
90
|
+
hook_event_name))
|
|
91
|
+
else:
|
|
92
|
+
logger.error(f"Unknown hook_event_name: {hook_event_name}")
|
|
93
|
+
sys.exit(1)
|
|
94
|
+
|
|
95
|
+
except json.JSONDecodeError as e:
|
|
96
|
+
logger.error(f"Failed to parse input JSON: {e}")
|
|
97
|
+
sys.exit(1)
|
|
98
|
+
except Exception as e:
|
|
99
|
+
logger.error(f"Routing error: {e}", exc_info=True)
|
|
100
|
+
sys.exit(1)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IDE Tools Router
|
|
3
|
+
|
|
4
|
+
Routes hook calls to appropriate IDE-specific routers based on --ide flag.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from modules.logs.audit_trail import AuditTrailLogger
|
|
11
|
+
from modules.logs.logger import MCPLogger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main(logger: MCPLogger, audit_logger: AuditTrailLogger, ide: str, context: Optional[str]):
|
|
15
|
+
"""
|
|
16
|
+
Main entry point for IDE tools - routes to appropriate IDE router
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
logger: MCPLogger instance
|
|
20
|
+
audit_logger: AuditTrailLogger instance
|
|
21
|
+
ide: IDE name (e.g., "cursor")
|
|
22
|
+
context: Additional context (to be verified as optional by the associated --ide handler)
|
|
23
|
+
"""
|
|
24
|
+
# Monkey patch sys.exit to log exit code
|
|
25
|
+
original_exit = sys.exit
|
|
26
|
+
|
|
27
|
+
def logged_exit(code=0):
|
|
28
|
+
logger.info(f"sys.exit called with code: {code}")
|
|
29
|
+
original_exit(code)
|
|
30
|
+
|
|
31
|
+
sys.exit = logged_exit
|
|
32
|
+
|
|
33
|
+
logger.info(f"IDE Tools router: ide={ide}, context=${context}")
|
|
34
|
+
|
|
35
|
+
# Read stdin input once at the top level (raw string)
|
|
36
|
+
# Each handler will parse it according to its own schema
|
|
37
|
+
stdin_input = sys.stdin.read()
|
|
38
|
+
|
|
39
|
+
# Route to appropriate IDE handler with the raw input string
|
|
40
|
+
if ide == "cursor":
|
|
41
|
+
from ide_tools.cursor import route_cursor_hook
|
|
42
|
+
route_cursor_hook(logger, audit_logger, stdin_input)
|
|
43
|
+
else:
|
|
44
|
+
logger.error(f"Unknown IDE: {ide}")
|
|
45
|
+
sys.exit(1)
|
|
@@ -45,13 +45,20 @@ def main():
|
|
|
45
45
|
logger.info('=' * 66)
|
|
46
46
|
logger.info('')
|
|
47
47
|
|
|
48
|
-
# Start config monitoring
|
|
49
|
-
config.start_monitoring(logger)
|
|
50
|
-
|
|
51
48
|
# Setup audit trail logging
|
|
52
49
|
audit_logger = setup_audit_trail_logger(logger)
|
|
53
50
|
|
|
54
|
-
|
|
51
|
+
if args.ide_tool:
|
|
52
|
+
from ide_tools.router import main as ide_tools_main
|
|
53
|
+
ide_tools_main(logger, audit_logger, args.ide, args.context)
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
# Continue with MCP wrapper mode
|
|
57
|
+
# Start config monitoring
|
|
58
|
+
config.start_monitoring(logger)
|
|
59
|
+
|
|
60
|
+
logger.info(
|
|
61
|
+
f"Starting MCPower Proxy:\n{{'args': {args}, 'log_file': {log_file}, 'log_level': {log_level}, 'debug_mode': {debug_mode}}}")
|
|
55
62
|
|
|
56
63
|
try:
|
|
57
64
|
# Parse JSON/JSONC config
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcpower-proxy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.72
|
|
4
4
|
Summary: MCPower Security proxy
|
|
5
5
|
Author-email: MCPower Security <support@mcpower.tech>
|
|
6
6
|
License: Apache License
|
|
@@ -209,14 +209,14 @@ 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
|
|
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
|
|
216
216
|
Requires-Dist: jsonc-parser>=1.1.5
|
|
217
217
|
Requires-Dist: jsonpath-ng>=1.7.0
|
|
218
218
|
Requires-Dist: pydantic>=2.8.0
|
|
219
|
-
Requires-Dist: mcpower-shared==0.1.
|
|
219
|
+
Requires-Dist: mcpower-shared==0.1.3
|
|
220
220
|
Dynamic: license-file
|
|
221
221
|
|
|
222
222
|
# MCPower Proxy
|
|
@@ -3,6 +3,12 @@ README.md
|
|
|
3
3
|
pyproject.toml
|
|
4
4
|
src/LICENSE
|
|
5
5
|
src/main.py
|
|
6
|
+
src/ide_tools/__init__.py
|
|
7
|
+
src/ide_tools/router.py
|
|
8
|
+
src/ide_tools/cursor/__init__.py
|
|
9
|
+
src/ide_tools/cursor/constants.py
|
|
10
|
+
src/ide_tools/cursor/format.py
|
|
11
|
+
src/ide_tools/cursor/router.py
|
|
6
12
|
src/mcpower_proxy.egg-info/PKG-INFO
|
|
7
13
|
src/mcpower_proxy.egg-info/SOURCES.txt
|
|
8
14
|
src/mcpower_proxy.egg-info/dependency_links.txt
|
|
@@ -10,6 +16,7 @@ src/mcpower_proxy.egg-info/entry_points.txt
|
|
|
10
16
|
src/mcpower_proxy.egg-info/requires.txt
|
|
11
17
|
src/mcpower_proxy.egg-info/top_level.txt
|
|
12
18
|
src/modules/__init__.py
|
|
19
|
+
src/modules/decision_handler.py
|
|
13
20
|
src/modules/apis/__init__.py
|
|
14
21
|
src/modules/apis/security_policy.py
|
|
15
22
|
src/modules/logs/__init__.py
|
|
@@ -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):
|
|
@@ -22,6 +23,7 @@ class SecurityAPIError(Exception):
|
|
|
22
23
|
|
|
23
24
|
class RateLimitExhaustedError(SecurityAPIError):
|
|
24
25
|
"""Security API rate limit exhausted (429) error"""
|
|
26
|
+
|
|
25
27
|
def __init__(self, message: str, retry_after: int = None):
|
|
26
28
|
super().__init__(message)
|
|
27
29
|
self.retry_after = retry_after
|
|
@@ -52,7 +54,7 @@ class SecurityPolicyClient:
|
|
|
52
54
|
if self.client:
|
|
53
55
|
await self.client.aclose()
|
|
54
56
|
|
|
55
|
-
async def inspect_policy_request(self, policy_request: PolicyRequest,
|
|
57
|
+
async def inspect_policy_request(self, policy_request: PolicyRequest,
|
|
56
58
|
prompt_id: str) -> InspectDecision:
|
|
57
59
|
"""Call inspect_policy_request API endpoint"""
|
|
58
60
|
if not self.client:
|
|
@@ -155,7 +157,7 @@ class SecurityPolicyClient:
|
|
|
155
157
|
audit_payload = {"payload": {"server": payload_dict["server"], "tools": payload_dict["tools"]}}
|
|
156
158
|
else:
|
|
157
159
|
audit_payload = {"payload": payload_dict}
|
|
158
|
-
|
|
160
|
+
|
|
159
161
|
self.audit_logger.log_event(
|
|
160
162
|
audit_event_type,
|
|
161
163
|
audit_payload,
|
|
@@ -166,6 +168,7 @@ class SecurityPolicyClient:
|
|
|
166
168
|
|
|
167
169
|
headers = {
|
|
168
170
|
"Content-Type": "application/json",
|
|
171
|
+
"User-Agent": f"MCPower-{__version__}",
|
|
169
172
|
"X-User-UID": self.user_id,
|
|
170
173
|
"X-App-UID": self.app_id
|
|
171
174
|
}
|
|
@@ -188,7 +191,8 @@ class SecurityPolicyClient:
|
|
|
188
191
|
raise SecurityAPIError(f"Unsupported HTTP method: {method}. Supported methods: POST, PUT")
|
|
189
192
|
|
|
190
193
|
on_make_request_duration = time.time() - on_make_request_start_time
|
|
191
|
-
self.logger.
|
|
194
|
+
self.logger.debug(
|
|
195
|
+
f"PROFILE: {method} id: {id} make_request duration: {on_make_request_duration:.2f} seconds url: {url}")
|
|
192
196
|
|
|
193
197
|
match response.status_code:
|
|
194
198
|
case 200:
|
|
@@ -215,7 +219,7 @@ class SecurityPolicyClient:
|
|
|
215
219
|
else:
|
|
216
220
|
# Other responses (e.g., /init) - log entire response
|
|
217
221
|
audit_result = {"result": data_dict}
|
|
218
|
-
|
|
222
|
+
|
|
219
223
|
self.audit_logger.log_event(
|
|
220
224
|
f"{audit_event_type}_result",
|
|
221
225
|
audit_result,
|
|
@@ -278,7 +282,8 @@ class SecurityPolicyClient:
|
|
|
278
282
|
def _handle_quota_restoration(self, endpoint: str):
|
|
279
283
|
"""Handle quota restoration (when non-429 response received)"""
|
|
280
284
|
if self.session_id in self._session_notification_times:
|
|
281
|
-
self.logger.info(
|
|
285
|
+
self.logger.info(
|
|
286
|
+
f"Quota restored - received successful response from {endpoint}. Session: {self.session_id}")
|
|
282
287
|
del self._session_notification_times[self.session_id]
|
|
283
288
|
|
|
284
289
|
def _send_throttled_quota_notification(self, retry_after: int, endpoint: str):
|