mcpower-proxy 0.0.65__py3-none-any.whl → 0.0.74__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.

Potentially problematic release.


This version of mcpower-proxy might be problematic. Click here for more details.

Files changed (39) hide show
  1. ide_tools/__init__.py +12 -0
  2. ide_tools/common/__init__.py +5 -0
  3. ide_tools/common/hooks/__init__.py +5 -0
  4. ide_tools/common/hooks/init.py +124 -0
  5. ide_tools/common/hooks/output.py +63 -0
  6. ide_tools/common/hooks/prompt_submit.py +133 -0
  7. ide_tools/common/hooks/read_file.py +167 -0
  8. ide_tools/common/hooks/shell_execution.py +255 -0
  9. ide_tools/common/hooks/shell_parser_bashlex.py +277 -0
  10. ide_tools/common/hooks/types.py +34 -0
  11. ide_tools/common/hooks/utils.py +286 -0
  12. ide_tools/cursor/__init__.py +11 -0
  13. ide_tools/cursor/constants.py +58 -0
  14. ide_tools/cursor/format.py +35 -0
  15. ide_tools/cursor/router.py +100 -0
  16. ide_tools/router.py +48 -0
  17. main.py +11 -4
  18. {mcpower_proxy-0.0.65.dist-info → mcpower_proxy-0.0.74.dist-info}/METADATA +4 -3
  19. mcpower_proxy-0.0.74.dist-info/RECORD +60 -0
  20. {mcpower_proxy-0.0.65.dist-info → mcpower_proxy-0.0.74.dist-info}/top_level.txt +1 -0
  21. modules/apis/security_policy.py +11 -6
  22. modules/decision_handler.py +219 -0
  23. modules/logs/audit_trail.py +16 -15
  24. modules/logs/logger.py +14 -18
  25. modules/redaction/gitleaks_rules.py +1 -1
  26. modules/redaction/pii_rules.py +0 -48
  27. modules/redaction/redactor.py +112 -107
  28. modules/ui/__init__.py +1 -1
  29. modules/ui/confirmation.py +0 -1
  30. modules/utils/cli.py +36 -6
  31. modules/utils/ids.py +55 -10
  32. modules/utils/json.py +3 -3
  33. wrapper/__version__.py +1 -1
  34. wrapper/middleware.py +135 -217
  35. wrapper/server.py +19 -11
  36. mcpower_proxy-0.0.65.dist-info/RECORD +0 -43
  37. {mcpower_proxy-0.0.65.dist-info → mcpower_proxy-0.0.74.dist-info}/WHEEL +0 -0
  38. {mcpower_proxy-0.0.65.dist-info → mcpower_proxy-0.0.74.dist-info}/entry_points.txt +0 -0
  39. {mcpower_proxy-0.0.65.dist-info → mcpower_proxy-0.0.74.dist-info}/licenses/LICENSE +0 -0
ide_tools/__init__.py ADDED
@@ -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,5 @@
1
+ """
2
+ Common IDE Tools
3
+
4
+ Shared logic for all IDE integrations.
5
+ """
@@ -0,0 +1,5 @@
1
+ """
2
+ Common Hook Handlers
3
+
4
+ Shared hook logic for all IDEs
5
+ """
@@ -0,0 +1,124 @@
1
+ """
2
+ Shared initialization logic for IDE tools
3
+
4
+ Registers IDE hooks with the security API.
5
+ """
6
+
7
+ import sys
8
+ from typing import Dict, Optional
9
+
10
+ from mcpower_shared.mcp_types import InitRequest, EnvironmentContext, ServerRef, ToolRef
11
+ from modules.apis.security_policy import SecurityPolicyClient
12
+ from modules.logs.audit_trail import AuditTrailLogger
13
+ from modules.logs.logger import MCPLogger
14
+ from modules.utils.ids import get_session_id, read_app_uid, get_project_mcpower_dir
15
+ from modules.utils.json import safe_json_dumps
16
+ from wrapper.__version__ import __version__
17
+
18
+
19
+ def output_init_result(success: bool, message: str):
20
+ """
21
+ Output init result to stdout
22
+
23
+ Args:
24
+ success: True if initialization succeeded
25
+ message: Status message
26
+ """
27
+ result = {
28
+ "success": success,
29
+ "message": message
30
+ }
31
+
32
+ print(safe_json_dumps(result), flush=True)
33
+
34
+
35
+ async def handle_init(
36
+ logger: MCPLogger,
37
+ audit_logger: AuditTrailLogger,
38
+ event_id: str,
39
+ cwd: Optional[str],
40
+ server_name: str,
41
+ client_name: str,
42
+ hooks: Dict[str, Dict[str, str]]
43
+ ) -> None:
44
+ """
45
+ Generic init handler - registers hooks with security API
46
+
47
+ Args:
48
+ logger: Logger instance
49
+ audit_logger: Audit logger instance
50
+ event_id: Event identifier
51
+ cwd: Current working directory
52
+ server_name: IDE-specific server name (e.g. "cursor_tools_mcp")
53
+ client_name: IDE-specific client name (e.g. "cursor", "claude-code")
54
+ hooks: Dict of hook definitions with {name, description, version}
55
+
56
+ Outputs result and exits with appropriate code.
57
+ """
58
+ session_id = get_session_id()
59
+
60
+ logger.info(f"Init handler started (client={client_name}, event_id={event_id}, cwd={cwd})")
61
+
62
+ try:
63
+ app_uid = read_app_uid(logger, get_project_mcpower_dir(cwd))
64
+ audit_logger.set_app_uid(app_uid)
65
+
66
+ audit_logger.log_event("mcpower_start", {
67
+ "wrapper_version": __version__,
68
+ "wrapped_server_name": server_name,
69
+ "client": client_name
70
+ })
71
+
72
+ try:
73
+ tools = [
74
+ ToolRef(
75
+ name=hook_info["name"],
76
+ description=hook_info["description"],
77
+ version=hook_info["version"]
78
+ )
79
+ for hook_info in hooks.values()
80
+ ]
81
+
82
+ init_request = InitRequest(
83
+ environment=EnvironmentContext(
84
+ session_id=session_id,
85
+ workspace={
86
+ "roots": [cwd] if cwd else [],
87
+ "current_files": []
88
+ },
89
+ client=client_name,
90
+ client_version=__version__,
91
+ selection_hash=""
92
+ ),
93
+ server=ServerRef(
94
+ name=server_name,
95
+ transport="stdio",
96
+ version="1.0.0",
97
+ context="ide"
98
+ ),
99
+ tools=tools
100
+ )
101
+
102
+ async with SecurityPolicyClient(
103
+ session_id=session_id,
104
+ logger=logger,
105
+ audit_logger=audit_logger,
106
+ app_id=app_uid
107
+ ) as client:
108
+ await client.init_tools(init_request, event_id=event_id)
109
+
110
+ logger.info(f"Hooks registered successfully for {client_name}")
111
+
112
+ # Success - output result and exit
113
+ output_init_result(True, f"{client_name.title()} hooks registered successfully")
114
+ sys.exit(0)
115
+
116
+ except Exception as e:
117
+ logger.error(f"API initialization failed: {e}")
118
+ output_init_result(False, f"Error: {str(e)}")
119
+ sys.exit(1)
120
+
121
+ except Exception as e:
122
+ logger.error(f"Unexpected error in init handler: {e}", exc_info=True)
123
+ output_init_result(False, f"Initialization failed: {str(e)}")
124
+ sys.exit(1)
@@ -0,0 +1,63 @@
1
+ """
2
+ IDE-agnostic output handling
3
+ """
4
+
5
+ import sys
6
+ from typing import Optional
7
+
8
+ from modules.logs.logger import MCPLogger
9
+ from .types import OutputFormat
10
+
11
+
12
+ def output_result(
13
+ logger: MCPLogger,
14
+ output_format: OutputFormat,
15
+ hook_type: str,
16
+ allowed: bool,
17
+ user_message: Optional[str] = None,
18
+ agent_message: Optional[str] = None
19
+ ) -> None:
20
+ """
21
+ Output hook result in IDE-specific format and exit with appropriate code
22
+
23
+ Args:
24
+ logger: Logger instance
25
+ output_format: IDE-specific output configuration
26
+ hook_type: "permission" or "continue"
27
+ allowed: True for allow/continue, False for deny/block
28
+ user_message: Optional message for user
29
+ agent_message: Optional message for agent/logs
30
+ """
31
+ # Format output using IDE-specific formatter
32
+ formatted_output = output_format.formatter(hook_type, allowed, user_message, agent_message)
33
+
34
+ logger.info(f"Hook output ({hook_type}, allowed={allowed}): {formatted_output}")
35
+ print(formatted_output, flush=True)
36
+
37
+ # Exit with appropriate code
38
+ exit_code = output_format.allow_exit_code if allowed else output_format.deny_exit_code
39
+ sys.exit(exit_code)
40
+
41
+
42
+ def output_error(
43
+ logger: MCPLogger,
44
+ output_format: OutputFormat,
45
+ hook_type: str,
46
+ error_message: str
47
+ ) -> None:
48
+ """
49
+ Output error and exit with error code
50
+
51
+ Args:
52
+ logger: Logger instance
53
+ output_format: IDE-specific output configuration
54
+ hook_type: "permission" or "continue"
55
+ error_message: Error message
56
+ """
57
+ logger.error(f"Hook error: {error_message}")
58
+
59
+ # Output as deny/block with error message
60
+ formatted_output = output_format.formatter(hook_type, False, error_message, error_message)
61
+ print(formatted_output, flush=True)
62
+
63
+ sys.exit(output_format.error_exit_code)
@@ -0,0 +1,133 @@
1
+ """
2
+ Shared logic for UserPromptSubmit hook
3
+ """
4
+
5
+ from typing import Dict, Any, Optional
6
+
7
+ from modules.logs.audit_trail import AuditTrailLogger
8
+ from modules.logs.logger import MCPLogger
9
+ from modules.redaction import redact
10
+ from modules.utils.ids import get_session_id, read_app_uid, get_project_mcpower_dir
11
+ from .output import output_result, output_error
12
+ from .types import HookConfig
13
+ from .utils import create_validator, extract_redaction_patterns, process_attachments_for_redaction, inspect_and_enforce
14
+
15
+
16
+ async def handle_prompt_submit(
17
+ logger: MCPLogger,
18
+ audit_logger: AuditTrailLogger,
19
+ stdin_input: str,
20
+ prompt_id: str,
21
+ event_id: str,
22
+ cwd: Optional[str],
23
+ config: HookConfig,
24
+ tool_name: str
25
+ ) -> None:
26
+ """
27
+ Shared handler for prompt submission hooks
28
+
29
+ Args:
30
+ logger: Logger instance
31
+ audit_logger: Audit logger instance
32
+ stdin_input: Raw JSON input
33
+ prompt_id: Prompt/conversation ID
34
+ event_id: Event/generation ID
35
+ cwd: Current working directory
36
+ config: IDE-specific hook configuration
37
+ tool_name: IDE-specific tool name (e.g., "beforeSubmitPrompt", "UserPromptSubmit")
38
+ """
39
+ session_id = get_session_id()
40
+ logger.info(
41
+ f"Prompt submit handler started (client={config.client_name}, prompt_id={prompt_id}, "
42
+ f"event_id={event_id}, cwd={cwd})")
43
+
44
+ app_uid = read_app_uid(logger, get_project_mcpower_dir(cwd))
45
+ audit_logger.set_app_uid(app_uid)
46
+
47
+ try:
48
+ try:
49
+ validator = create_validator(
50
+ required_fields={"prompt": str},
51
+ optional_fields={"attachments": list}
52
+ )
53
+ input_data = validator(stdin_input)
54
+ prompt = input_data["prompt"]
55
+ attachments = input_data.get("attachments", [])
56
+ except ValueError as e:
57
+ logger.error(f"Input validation error: {e}")
58
+ output_error(logger, config.output_format, "continue", str(e))
59
+ return
60
+
61
+ redacted_prompt = redact(prompt)
62
+
63
+ audit_logger.log_event(
64
+ "prompt_submission",
65
+ {
66
+ "server": config.server_name,
67
+ "tool": tool_name,
68
+ "params": {"prompt": f"{redacted_prompt[:20]}...", "attachments_count": len(attachments)}
69
+ },
70
+ event_id=event_id
71
+ )
72
+
73
+ prompt_patterns = extract_redaction_patterns(redacted_prompt)
74
+
75
+ # Check for redactions in file attachments
76
+ files_with_redactions = process_attachments_for_redaction(
77
+ attachments,
78
+ logger
79
+ )
80
+
81
+ has_any_redactions = bool(prompt_patterns) or len(files_with_redactions) > 0
82
+
83
+ content_data: Dict[str, Any] = {
84
+ "prompt": redacted_prompt,
85
+ "is_redacted": has_any_redactions,
86
+ "redacted_files": files_with_redactions,
87
+ }
88
+
89
+ # Call security API and enforce decision
90
+ try:
91
+ decision = await inspect_and_enforce(
92
+ is_request=True,
93
+ session_id=session_id,
94
+ logger=logger,
95
+ audit_logger=audit_logger,
96
+ app_uid=app_uid,
97
+ event_id=event_id,
98
+ server_name=config.server_name,
99
+ tool_name=tool_name,
100
+ content_data=content_data,
101
+ prompt_id=prompt_id,
102
+ cwd=cwd,
103
+ client_name=config.client_name
104
+ )
105
+
106
+ audit_logger.log_event(
107
+ "prompt_submission_forwarded",
108
+ {
109
+ "server": config.server_name,
110
+ "tool": tool_name,
111
+ "params": {"redactions_found": has_any_redactions}
112
+ },
113
+ event_id=event_id
114
+ )
115
+
116
+ reasons = decision.get("reasons", [])
117
+ agent_message = "Prompt submission approved: {0}".format("; ".join(
118
+ reasons)) if reasons else "Prompt submission approved by security policy"
119
+ output_result(logger, config.output_format, "continue", True,
120
+ "Prompt approved", agent_message)
121
+
122
+ except Exception as e:
123
+ # Decision enforcement failed - block
124
+ error_msg = str(e)
125
+ user_message = "Prompt blocked by security policy"
126
+ if "User blocked" in error_msg or "User denied" in error_msg:
127
+ user_message = "Prompt blocked by user"
128
+
129
+ output_result(logger, config.output_format, "continue", False, user_message, error_msg)
130
+
131
+ except Exception as e:
132
+ logger.error(f"Unexpected error in prompt submit handler: {e}", exc_info=True)
133
+ output_error(logger, config.output_format, "continue", f"Unexpected error: {str(e)}")
@@ -0,0 +1,167 @@
1
+ """
2
+ Shared logic for beforeReadFile/PreReadFile hook
3
+ """
4
+
5
+ from typing import Optional
6
+
7
+ from modules.logs.audit_trail import AuditTrailLogger
8
+ from modules.logs.logger import MCPLogger
9
+ from modules.redaction import redact
10
+ from modules.utils.ids import get_session_id, read_app_uid, get_project_mcpower_dir
11
+ from .output import output_result, output_error
12
+ from .types import HookConfig
13
+ from .utils import create_validator, process_attachments_for_redaction, inspect_and_enforce
14
+
15
+
16
+ async def handle_read_file(
17
+ logger: MCPLogger,
18
+ audit_logger: AuditTrailLogger,
19
+ stdin_input: str,
20
+ prompt_id: str,
21
+ event_id: str,
22
+ cwd: Optional[str],
23
+ config: HookConfig,
24
+ tool_name: str
25
+ ) -> None:
26
+ """
27
+ Shared handler for file read hooks
28
+
29
+ Args:
30
+ logger: Logger instance
31
+ audit_logger: Audit logger instance
32
+ stdin_input: Raw JSON input
33
+ prompt_id: Prompt/conversation ID
34
+ event_id: Event/generation ID
35
+ cwd: Current working directory
36
+ config: IDE-specific hook configuration
37
+ tool_name: IDE-specific tool name (e.g., "beforeReadFile", "PreToolUse")
38
+ """
39
+ session_id = get_session_id()
40
+ logger.info(
41
+ f"Read file handler started (client={config.client_name}, prompt_id={prompt_id}, event_id={event_id}, cwd={cwd})")
42
+
43
+ app_uid = read_app_uid(logger, get_project_mcpower_dir(cwd))
44
+ audit_logger.set_app_uid(app_uid)
45
+
46
+ try:
47
+ try:
48
+ validator = create_validator(
49
+ required_fields={"file_path": str, "content": str},
50
+ optional_fields={"attachments": list}
51
+ )
52
+ input_data = validator(stdin_input)
53
+ file_path = input_data["file_path"]
54
+ provided_content = input_data["content"]
55
+ attachments = input_data.get("attachments", [])
56
+ except ValueError as e:
57
+ logger.error(f"Input validation error: {e}")
58
+ output_error(logger, config.output_format, "permission", str(e))
59
+ return
60
+
61
+ audit_logger.log_event(
62
+ "agent_request",
63
+ {
64
+ "server": config.server_name,
65
+ "tool": tool_name,
66
+ "params": {"file_path": file_path, "attachments_count": len(attachments)}
67
+ },
68
+ event_id=event_id
69
+ )
70
+
71
+ # Check content length - skip API if too large
72
+ if len(provided_content) > config.max_content_length:
73
+ logger.info(f"Content length ({len(provided_content)} chars) exceeds max ({config.max_content_length}) - "
74
+ f"skipping API call")
75
+
76
+ audit_logger.log_event(
77
+ "agent_request_forwarded",
78
+ {
79
+ "server": config.server_name,
80
+ "tool": tool_name,
81
+ "params": {
82
+ "file_path": file_path,
83
+ "content_length": len(provided_content),
84
+ "content_too_large": True
85
+ }
86
+ },
87
+ event_id=event_id
88
+ )
89
+
90
+ output_result(logger, config.output_format, "permission", True)
91
+ return
92
+
93
+ # Redact the main content
94
+ redacted_content = redact(provided_content)
95
+
96
+ # Process attachments for redaction status
97
+ files_with_redactions = process_attachments_for_redaction(attachments, logger)
98
+ files_with_redactions_paths = {f["file_path"] for f in files_with_redactions}
99
+
100
+ # Build attachments info with redaction status
101
+ attachments_info = []
102
+ for attachment in attachments:
103
+ att_path = attachment.get("file_path") or attachment.get("filePath")
104
+ if att_path:
105
+ attachments_info.append({
106
+ "file_path": att_path,
107
+ "has_redactions": att_path in files_with_redactions_paths
108
+ })
109
+
110
+ logger.info(f"Processed file and {len(attachments)} attachment(s), found redactions in "
111
+ f"{len(files_with_redactions)} attachment(s)")
112
+
113
+ # Build content_data with file_path, redacted content, and attachments
114
+ content_data = {
115
+ "file_path": file_path,
116
+ "content": redacted_content,
117
+ "attachments": attachments_info
118
+ }
119
+
120
+ # Call security API and enforce decision
121
+ try:
122
+ decision = await inspect_and_enforce(
123
+ is_request=True,
124
+ session_id=session_id,
125
+ logger=logger,
126
+ audit_logger=audit_logger,
127
+ app_uid=app_uid,
128
+ event_id=event_id,
129
+ server_name=config.server_name,
130
+ tool_name=tool_name,
131
+ content_data=content_data,
132
+ prompt_id=prompt_id,
133
+ cwd=cwd,
134
+ current_files=[file_path],
135
+ client_name=config.client_name
136
+ )
137
+
138
+ audit_logger.log_event(
139
+ "agent_request_forwarded",
140
+ {
141
+ "server": config.server_name,
142
+ "tool": tool_name,
143
+ "params": {
144
+ "file_path": file_path,
145
+ "content_length": len(provided_content),
146
+ "attachments_with_redactions": len(files_with_redactions)}
147
+ },
148
+ event_id=event_id
149
+ )
150
+
151
+ reasons = decision.get("reasons", [])
152
+ agent_message = "File read approved: " + "; ".join(
153
+ reasons) if reasons else "File read approved by security policy"
154
+ output_result(logger, config.output_format, "permission", True, "File read approved", agent_message)
155
+
156
+ except Exception as e:
157
+ # Decision enforcement failed - block
158
+ error_msg = str(e)
159
+ user_message = "File read blocked by security policy"
160
+ if "User blocked" in error_msg or "User denied" in error_msg:
161
+ user_message = "File read blocked by user"
162
+
163
+ output_result(logger, config.output_format, "permission", False, user_message, error_msg)
164
+
165
+ except Exception as e:
166
+ logger.error(f"Unexpected error in read file handler: {e}", exc_info=True)
167
+ output_error(logger, config.output_format, "permission", f"Unexpected error: {str(e)}")