mcpower-proxy 0.0.58__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.
Files changed (43) hide show
  1. main.py +112 -0
  2. mcpower_proxy-0.0.58.dist-info/METADATA +250 -0
  3. mcpower_proxy-0.0.58.dist-info/RECORD +43 -0
  4. mcpower_proxy-0.0.58.dist-info/WHEEL +5 -0
  5. mcpower_proxy-0.0.58.dist-info/entry_points.txt +2 -0
  6. mcpower_proxy-0.0.58.dist-info/licenses/LICENSE +201 -0
  7. mcpower_proxy-0.0.58.dist-info/top_level.txt +3 -0
  8. modules/__init__.py +1 -0
  9. modules/apis/__init__.py +1 -0
  10. modules/apis/security_policy.py +322 -0
  11. modules/logs/__init__.py +1 -0
  12. modules/logs/audit_trail.py +162 -0
  13. modules/logs/logger.py +128 -0
  14. modules/redaction/__init__.py +13 -0
  15. modules/redaction/constants.py +38 -0
  16. modules/redaction/gitleaks_rules.py +1268 -0
  17. modules/redaction/pii_rules.py +271 -0
  18. modules/redaction/redactor.py +599 -0
  19. modules/ui/__init__.py +1 -0
  20. modules/ui/classes.py +48 -0
  21. modules/ui/confirmation.py +200 -0
  22. modules/ui/simple_dialog.py +104 -0
  23. modules/ui/xdialog/__init__.py +249 -0
  24. modules/ui/xdialog/constants.py +13 -0
  25. modules/ui/xdialog/mac_dialogs.py +190 -0
  26. modules/ui/xdialog/tk_dialogs.py +78 -0
  27. modules/ui/xdialog/windows_custom_dialog.py +426 -0
  28. modules/ui/xdialog/windows_dialogs.py +250 -0
  29. modules/ui/xdialog/windows_structs.py +183 -0
  30. modules/ui/xdialog/yad_dialogs.py +236 -0
  31. modules/ui/xdialog/zenity_dialogs.py +156 -0
  32. modules/utils/__init__.py +1 -0
  33. modules/utils/cli.py +46 -0
  34. modules/utils/config.py +193 -0
  35. modules/utils/copy.py +36 -0
  36. modules/utils/ids.py +160 -0
  37. modules/utils/json.py +120 -0
  38. modules/utils/mcp_configs.py +48 -0
  39. wrapper/__init__.py +1 -0
  40. wrapper/__version__.py +6 -0
  41. wrapper/middleware.py +750 -0
  42. wrapper/schema.py +227 -0
  43. wrapper/server.py +78 -0
wrapper/schema.py ADDED
@@ -0,0 +1,227 @@
1
+ from typing import Any, Dict, List, Optional
2
+
3
+
4
+ class ZTypeBase:
5
+ """Base class for all schema types"""
6
+
7
+ def __init__(self, description: Optional[str] = None):
8
+ self.description = description
9
+ self._optional = False
10
+
11
+ def describe(self, description: str):
12
+ """Add description to the field"""
13
+ self.description = description
14
+ return self
15
+
16
+ def optional(self):
17
+ """Mark field as optional"""
18
+ self._optional = True
19
+ return self
20
+
21
+ def to_json_schema(self) -> Dict[str, Any]:
22
+ """Convert to JSON schema format"""
23
+ raise NotImplementedError
24
+
25
+
26
+ class ZString(ZTypeBase):
27
+ def to_json_schema(self) -> Dict[str, Any]:
28
+ schema = {}
29
+ if self.description:
30
+ schema["description"] = self.description
31
+ schema["type"] = "string"
32
+ return schema
33
+
34
+
35
+ class ZNumber(ZTypeBase):
36
+ def to_json_schema(self) -> Dict[str, Any]:
37
+ schema = {}
38
+ if self.description:
39
+ schema["description"] = self.description
40
+ schema["type"] = "number"
41
+ return schema
42
+
43
+
44
+ class ZBoolean(ZTypeBase):
45
+ def to_json_schema(self) -> Dict[str, Any]:
46
+ schema = {}
47
+ if self.description:
48
+ schema["description"] = self.description
49
+ schema["type"] = "boolean"
50
+ return schema
51
+
52
+
53
+ class ZEnum(ZTypeBase):
54
+ def __init__(self, values: List[str], description: Optional[str] = None):
55
+ super().__init__(description)
56
+ self.values = values
57
+
58
+ def to_json_schema(self) -> Dict[str, Any]:
59
+ schema = {}
60
+ if self.description:
61
+ schema["description"] = self.description
62
+ schema.update({
63
+ "type": "string",
64
+ "enum": self.values
65
+ })
66
+ return schema
67
+
68
+
69
+ class ZArray(ZTypeBase):
70
+ def __init__(self, item_type: ZTypeBase, description: Optional[str] = None):
71
+ super().__init__(description)
72
+ self.item_type = item_type
73
+
74
+ def to_json_schema(self) -> Dict[str, Any]:
75
+ schema = {}
76
+ if self.description:
77
+ schema["description"] = self.description
78
+ schema.update({
79
+ "type": "array",
80
+ "items": self.item_type.to_json_schema()
81
+ })
82
+ return schema
83
+
84
+
85
+ class ZObject(ZTypeBase):
86
+ def __init__(self, properties: Dict[str, ZTypeBase],
87
+ description: Optional[str] = None):
88
+ super().__init__(description)
89
+ self.properties = properties
90
+
91
+ def to_json_schema(self) -> Dict[str, Any]:
92
+ schema = {}
93
+ if self.description:
94
+ schema["description"] = self.description
95
+
96
+ # Automatically determine required fields - any property that is NOT optional
97
+ required_fields = [key for key, prop in self.properties.items() if not prop._optional]
98
+
99
+ schema.update({
100
+ "type": "object",
101
+ "properties": {
102
+ key: prop.to_json_schema()
103
+ for key, prop in self.properties.items()
104
+ }
105
+ })
106
+
107
+ if required_fields:
108
+ schema["required"] = required_fields
109
+
110
+ return schema
111
+
112
+
113
+ # Define the schema using the Zod-like API
114
+ ContentItemSchema = ZObject({
115
+ "type": ZEnum(["text", "code", "file", "image", "artifact", "data"]),
116
+ "content": ZString().describe(
117
+ "FORBIDDEN: NEVER use descriptions or summaries.\n"
118
+ "MANDATORY: Full base64 data for all binary files.\n"
119
+ "(-$500 penalty for violations)"
120
+ ),
121
+ "metadata": ZObject({
122
+ "language": ZString().describe("Coding language").optional(),
123
+ "filename": ZString().describe("File name").optional(),
124
+ "mimeType": ZString().describe("MANDATORY for binary files - exact MIME type required").optional(),
125
+ "size": ZNumber().describe("File size").optional(),
126
+ "encoding": ZString().describe("MUST be 'base64' for encoded content").optional(),
127
+ "title": ZString().describe("Artifact title").optional(),
128
+ "description": ZString().optional(),
129
+ "isBase64": ZBoolean().describe("MUST be true for base64 content").optional(),
130
+ "originalSource": ZEnum(["user", "assistant"]).describe(
131
+ "Set to 'assistant' for model-generated content. FORBIDDEN to mix sources "
132
+ ).optional()
133
+ }).optional()
134
+ })
135
+
136
+ # Input Request Schema - the additional fields to be added to all tool schemas
137
+ InputRequestSchema = ZObject({
138
+ "__wrapper_contextSummary": ZString().describe("MANDATORY: summary of the chat context. MUST ALWAYS BE PROVIDED."),
139
+ "__wrapper_userPrompt": ZString().describe(
140
+ "MANDATORY: Most recent user message that led to this tool call - NO MODIFICATIONS. MUST ALWAYS BE PROVIDED."),
141
+ "__wrapper_userPromptId": ZString().describe(
142
+ "MANDATORY: Correlation ID for this user request flow. "
143
+ "RULES: "
144
+ "1. If this is the FIRST tool call for a user input: Generate a new truly-random 8-characters-long ID "
145
+ "2. If this is a SUBSEQUENT tool call for the SAME user input: Use the SAME ID from step 1 "
146
+ "3. Only generate a NEW ID when responding to a NEW user message "
147
+ "The agent MUST track this ID throughout the entire response flow."
148
+ ),
149
+ "__wrapper_modelIntent": ZString().describe(
150
+ "MANDATORY: What the agent plans to accomplish. MUST ALWAYS BE PROVIDED."),
151
+ "__wrapper_modelPlan": ZString().describe("Step-by-step execution plan").optional(),
152
+ "__wrapper_modelExpectedOutputs": ZString().describe("What the agent expects to receive").optional(),
153
+ "__wrapper_currentFiles": ZString().describe(
154
+ "ALL context-related file paths (active file, open tabs, referenced files, etc.) - comma-separated list"
155
+ ).optional(),
156
+ })
157
+
158
+
159
+ # Remove the buggy EvaluationRequestSchema reference
160
+
161
+ def merge_input_schema_with_existing(existing_schema: Optional[Dict[str, Any]]) -> Dict[str, Any]:
162
+ """
163
+ Merge the InputRequestSchema with an existing tool's input schema.
164
+ If existing_schema is None, returns just the InputRequestSchema.
165
+ If existing_schema exists, merges the properties and required fields.
166
+ """
167
+ input_request_json = get_input_request_schema()
168
+
169
+ if not existing_schema:
170
+ return input_request_json
171
+
172
+ # If existing schema is not an object type, wrap it or replace it
173
+ if existing_schema.get("type") != "object":
174
+ # Create a new object schema with the existing as a nested property
175
+ return {
176
+ "type": "object",
177
+ "properties": {
178
+ **input_request_json["properties"],
179
+ **existing_schema.get("properties", {}),
180
+ },
181
+ "required": input_request_json.get("required", [])
182
+ }
183
+
184
+ # Merge object schemas
185
+ merged_properties = {
186
+ **existing_schema.get("properties", {}),
187
+ **input_request_json["properties"]
188
+ }
189
+
190
+ # Merge required fields
191
+ existing_required = existing_schema.get("required", [])
192
+ input_required = input_request_json.get("required", [])
193
+ merged_required = list(set(existing_required + input_required))
194
+
195
+ merged_schema = {
196
+ "type": "object",
197
+ "properties": merged_properties
198
+ }
199
+
200
+ if merged_required:
201
+ merged_schema["required"] = merged_required
202
+
203
+ # Preserve description from existing schema if present
204
+ if existing_schema.get("description"):
205
+ merged_schema["description"] = existing_schema["description"]
206
+
207
+ return merged_schema
208
+
209
+
210
+ # Main function to get JSON schema (equivalent to z.toJSONSchema())
211
+ def to_json_schema(schema: ZTypeBase) -> Dict[str, Any]:
212
+ """
213
+ Convert schema to JSON Schema format.
214
+ This is the equivalent of z.toJSONSchema() in TypeScript.
215
+ """
216
+ base_schema = schema.to_json_schema()
217
+
218
+ return base_schema
219
+
220
+
221
+ # Export the main functions
222
+ def get_input_request_schema() -> Dict[str, Any]:
223
+ """
224
+ Get the JSON schema for InputRequest.
225
+ This is equivalent to z.toJSONSchema(InputRequestSchema) in TypeScript.
226
+ """
227
+ return to_json_schema(InputRequestSchema)
wrapper/server.py ADDED
@@ -0,0 +1,78 @@
1
+ """
2
+ FastMCP-based wrapper server with ProxyClient integration
3
+ Implements transparent 1:1 MCP proxying with security middleware
4
+ """
5
+
6
+ import logging
7
+
8
+ from fastmcp.server.middleware.logging import StructuredLoggingMiddleware
9
+ from fastmcp.server.proxy import ProxyClient, default_proxy_roots_handler, FastMCPProxy
10
+
11
+ from modules.logs.audit_trail import AuditTrailLogger
12
+ from modules.logs.logger import MCPLogger
13
+ from .__version__ import __version__
14
+ from .middleware import SecurityMiddleware
15
+
16
+
17
+ def create_wrapper_server(wrapper_server_name: str,
18
+ wrapped_server_configs: dict,
19
+ logger: MCPLogger,
20
+ audit_logger: AuditTrailLogger,
21
+ log_level: int = logging.INFO
22
+ ) -> FastMCPProxy:
23
+ """
24
+ Create a FastMCP wrapper server with security middleware and security-aware handlers
25
+
26
+ Args:
27
+ wrapper_server_name: Name for the wrapper MCP server
28
+ wrapped_server_configs: FastMCP MCPConfig format dictionary
29
+ api_url: URL of security policy API (defaults to config value)
30
+ log_level: Logging level (e.g., logging.DEBUG, logging.INFO)
31
+ logger: logger instance
32
+
33
+ Returns:
34
+ Configured FastMCPProxy server instance
35
+ """
36
+
37
+ # Create a security middleware instance first so we can use its handlers
38
+ security_middleware = SecurityMiddleware(
39
+ wrapped_server_configs=wrapped_server_configs,
40
+ wrapper_server_name=wrapper_server_name,
41
+ wrapper_server_version=__version__,
42
+ logger=logger,
43
+ audit_logger=audit_logger
44
+ )
45
+
46
+ # Log MCPower startup to audit trail
47
+ audit_logger.log_event("mcpower_start", {
48
+ "wrapper_version": __version__,
49
+ "wrapped_server_name": security_middleware.wrapped_server_name,
50
+ "wrapped_server_configs": wrapped_server_configs
51
+ })
52
+
53
+ # Create FastMCP server as proxy with our security-aware ProxyClient
54
+ def client_factory():
55
+ return ProxyClient(
56
+ wrapped_server_configs,
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
+ )
64
+
65
+ server = FastMCPProxy(client_factory=client_factory, name=wrapper_server_name, version=__version__)
66
+
67
+ # Add FastMCP's structured logging middleware (always enabled)
68
+ # Use the log level passed from main.py
69
+ logging_middleware = StructuredLoggingMiddleware(
70
+ logger=logger.logger,
71
+ log_level=log_level
72
+ )
73
+ server.add_middleware(logging_middleware)
74
+
75
+ # Add security middleware (runs after logging)
76
+ server.add_middleware(security_middleware)
77
+
78
+ return server