agent-mcp-gateway 0.2.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.
@@ -0,0 +1,18 @@
1
+ src/CONFIG_README.md,sha256=RDrVI5aP5Wld0_HsHoc8tdyC1IO_JzhFee_3yuLhBwo,9201
2
+ src/__init__.py,sha256=_xMCrvLcZjUcPIDAoeorVOwswrLgrKtrf_g1178faM8,55
3
+ src/audit.py,sha256=P_YMhSifH66Fm_HXt6wFuFRmk0ekzXXzJOAc9zTinwQ,3034
4
+ src/config.py,sha256=_Q0Fo5Nc22OnqubGJyZi92QMaMF38QH7SXVQYN-0-IU,32793
5
+ src/config_watcher.py,sha256=z8MsOexNEEsmCtk7NBcKpkaLyYLbDfJF10euh3pAvIk,11603
6
+ src/gateway.py,sha256=phk1dyCq3l-ZLbtEXCCaa_CE6vM2tZGrGnyDwt0OHK8,22205
7
+ src/main.py,sha256=4ReyFFmXTl6OdMEiJ4UEiG_gVHsACAQeF8EJ6rSvvYU,24326
8
+ src/metrics.py,sha256=SD4e5zo4Gtd1Gqp7SIHo7DBZTrvqaY-iKtf4qAPTy_k,9896
9
+ src/middleware.py,sha256=iaHxe7lW0nSz3k-ieNyO4n5MtPKSGChEg_qRlHr3X8Q,7257
10
+ src/policy.py,sha256=6jjWjExkr5DsFsmxCdxUq5Is8f1HV_x-KEJxNM1LM9s,19308
11
+ src/proxy.py,sha256=ueSEm5t5AOYVuB24DAvnhdduEmiDmpvLqhnGyoJtsD8,24949
12
+ src/config/.mcp-gateway-rules.json.example,sha256=UNfIVbtvHddWhWmWJ6Om-iaoPXNSDkzsUKtA59gi7ac,1289
13
+ src/config/.mcp.json.example,sha256=_Gy7bflSOw15A2LfXpprSf29gGcve4zjDka-vuPVN2c,842
14
+ agent_mcp_gateway-0.2.1.dist-info/METADATA,sha256=g2zqAYeFQQbalxwbjemR6xf09JU4u3Ab7agHGg20PW8,47901
15
+ agent_mcp_gateway-0.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ agent_mcp_gateway-0.2.1.dist-info/entry_points.txt,sha256=Rb9I44QKFEI6-38dCb1sPwSAzQlAzUBZKDQqjb-FHTI,52
17
+ agent_mcp_gateway-0.2.1.dist-info/licenses/LICENSE,sha256=XPIqCRndYgq7mXJ2NlfnvN0IoqQGXpK2gnHIqyvuPws,1078
18
+ agent_mcp_gateway-0.2.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ agent-mcp-gateway = src.main:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Rodrigo Franken Dutra
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
src/CONFIG_README.md ADDED
@@ -0,0 +1,351 @@
1
+ # Configuration Module
2
+
3
+ The configuration module (`src/config.py`) provides robust loading, validation, and management of configuration files for the Agent MCP Gateway.
4
+
5
+ ## Features
6
+
7
+ - **MCP Server Configuration**: Load and validate MCP server definitions (stdio and HTTP transports)
8
+ - **Gateway Rules**: Load and validate agent policy configurations
9
+ - **Environment Variable Substitution**: Automatic `${VAR}` pattern replacement
10
+ - **Comprehensive Validation**: Type checking, required field validation, and cross-validation
11
+ - **Clear Error Messages**: Helpful, actionable error messages for all validation failures
12
+ - **Path Expansion**: Automatic `~/` home directory expansion
13
+
14
+ ## Functions
15
+
16
+ ### `load_mcp_config(path: str) -> dict`
17
+
18
+ Loads and validates MCP server configuration from a JSON file.
19
+
20
+ **Features:**
21
+ - Validates stdio transport (command, args, env)
22
+ - Validates HTTP transport (url, headers)
23
+ - Performs environment variable substitution
24
+ - Validates required fields per transport type
25
+ - Expands user paths (`~/`)
26
+
27
+ **Example:**
28
+ ```python
29
+ from src.config import load_mcp_config
30
+
31
+ config = load_mcp_config("./config/.mcp.json")
32
+ servers = config["mcpServers"]
33
+ ```
34
+
35
+ ### `load_gateway_rules(path: str) -> dict`
36
+
37
+ Loads and validates gateway rules configuration from a JSON file.
38
+
39
+ **Features:**
40
+ - Validates agent policy structure
41
+ - Supports hierarchical agent names (`team.role`)
42
+ - Validates wildcard patterns (`*`, `get_*`, `*_query`)
43
+ - Validates allow/deny rules
44
+ - Expands user paths (`~/`)
45
+
46
+ **Example:**
47
+ ```python
48
+ from src.config import load_gateway_rules
49
+
50
+ rules = load_gateway_rules(".mcp-gateway-rules.json")
51
+ agents = rules["agents"]
52
+ ```
53
+
54
+ ### `validate_rules_against_servers(rules: dict, mcp_config: dict) -> list[str]`
55
+
56
+ Cross-validates that all servers referenced in rules exist in the MCP configuration.
57
+
58
+ **Returns:** List of warning messages (empty if all valid)
59
+
60
+ **Example:**
61
+ ```python
62
+ from src.config import load_mcp_config, load_gateway_rules, validate_rules_against_servers
63
+
64
+ mcp_config = load_mcp_config("./config/.mcp.json")
65
+ rules = load_gateway_rules(".mcp-gateway-rules.json")
66
+
67
+ warnings = validate_rules_against_servers(rules, mcp_config)
68
+ for warning in warnings:
69
+ print(f"Warning: {warning}")
70
+ ```
71
+
72
+ ### `get_config_path(env_var: str, default: str) -> str`
73
+
74
+ Gets configuration file path from environment variable or uses default.
75
+
76
+ **Example:**
77
+ ```python
78
+ from src.config import get_config_path
79
+
80
+ mcp_config_path = get_config_path(
81
+ "GATEWAY_MCP_CONFIG",
82
+ "./config/.mcp.json"
83
+ )
84
+ ```
85
+
86
+ ## Configuration File Formats
87
+
88
+ ### MCP Servers Configuration
89
+
90
+ **File:** `config/.mcp.json`
91
+
92
+ **Structure:**
93
+ ```json
94
+ {
95
+ "mcpServers": {
96
+ "server-name": {
97
+ "command": "npx",
98
+ "args": ["-y", "package-name"],
99
+ "env": {
100
+ "VAR_NAME": "${ENV_VAR}"
101
+ }
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ **Stdio Transport:**
108
+ ```json
109
+ {
110
+ "server-name": {
111
+ "command": "npx", // Required
112
+ "args": ["--flag", "value"], // Optional
113
+ "env": {"KEY": "value"} // Optional
114
+ }
115
+ }
116
+ ```
117
+
118
+ **HTTP Transport:**
119
+ ```json
120
+ {
121
+ "server-name": {
122
+ "url": "https://example.com/mcp", // Required
123
+ "headers": { // Optional
124
+ "Authorization": "Bearer token"
125
+ }
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### Gateway Rules Configuration
131
+
132
+ **File:** `.mcp-gateway-rules.json`
133
+
134
+ **Structure:**
135
+ ```json
136
+ {
137
+ "agents": {
138
+ "agent-id": {
139
+ "allow": {
140
+ "servers": ["server1", "server2"],
141
+ "tools": {
142
+ "server1": ["tool1", "tool2", "get_*"],
143
+ "server2": ["*"]
144
+ }
145
+ },
146
+ "deny": {
147
+ "tools": {
148
+ "server1": ["drop_*", "delete_*"]
149
+ }
150
+ }
151
+ }
152
+ },
153
+ "defaults": {
154
+ "deny_on_missing_agent": true
155
+ }
156
+ }
157
+ ```
158
+
159
+ **Wildcard Patterns:**
160
+ - `*` - Matches all (must be alone)
161
+ - `get_*` - Matches prefix
162
+ - `*_query` - Matches suffix
163
+ - Middle wildcards NOT supported (`get_*_all` is invalid)
164
+
165
+ **Hierarchical Agent Names:**
166
+ - `researcher` - Simple name
167
+ - `team.researcher` - Hierarchical (team.role)
168
+ - `company.team.developer` - Multi-level hierarchy
169
+
170
+ ## Environment Variables
171
+
172
+ ### Configuration Paths
173
+
174
+ - `GATEWAY_MCP_CONFIG` - Path to MCP servers config (default: `./config/.mcp.json`)
175
+ - `GATEWAY_RULES` - Path to gateway rules config (default: `.mcp-gateway-rules.json`, fallback: `./config/.mcp-gateway-rules.json`)
176
+
177
+ ### Variable Substitution
178
+
179
+ Environment variables in configuration files are substituted using `${VAR}` syntax:
180
+
181
+ ```json
182
+ {
183
+ "env": {
184
+ "API_KEY": "${BRAVE_API_KEY}",
185
+ "DATABASE_URL": "${POSTGRES_URL}"
186
+ }
187
+ }
188
+ ```
189
+
190
+ **Important:** All referenced environment variables MUST be set before loading the configuration, or a `ValueError` will be raised with a clear error message.
191
+
192
+ ## Error Handling
193
+
194
+ The configuration module provides clear, actionable error messages for all validation failures:
195
+
196
+ ### Missing File
197
+ ```
198
+ FileNotFoundError: MCP server configuration file not found: /path/to/config.json
199
+ ```
200
+
201
+ ### Invalid JSON
202
+ ```
203
+ JSONDecodeError: Invalid JSON in MCP server configuration: Expecting ',' delimiter
204
+ ```
205
+
206
+ ### Missing Environment Variable
207
+ ```
208
+ ValueError: Environment variable "BRAVE_API_KEY" referenced in configuration but not set.
209
+ Please set this variable before starting the gateway.
210
+ ```
211
+
212
+ ### Invalid Transport
213
+ ```
214
+ ValueError: Server "my-server" must specify either "command" (stdio) or "url" (HTTP) transport
215
+ ```
216
+
217
+ ### Invalid Wildcard
218
+ ```
219
+ ValueError: Agent "test" allow.tools["server"][0]: wildcard in pattern "get_*_all"
220
+ must be at start, end, or alone
221
+ ```
222
+
223
+ ## Validation Script
224
+
225
+ Use the included validation script to check your configurations:
226
+
227
+ ```bash
228
+ # Validate with default paths
229
+ python validate_config.py
230
+
231
+ # Validate with custom paths
232
+ GATEWAY_MCP_CONFIG=./my-config.json \
233
+ GATEWAY_RULES=./.mcp-gateway-rules.json \
234
+ python validate_config.py
235
+
236
+ # Validate with required environment variables
237
+ BRAVE_API_KEY=your_key \
238
+ POSTGRES_URL=postgresql://localhost/db \
239
+ python validate_config.py
240
+ ```
241
+
242
+ ## Testing
243
+
244
+ Run the test suite to verify the configuration module:
245
+
246
+ ```bash
247
+ python test_config.py
248
+ ```
249
+
250
+ **Tests cover:**
251
+ - Valid configuration loading
252
+ - Environment variable substitution
253
+ - Missing environment variables
254
+ - HTTP and stdio transports
255
+ - Invalid transport specifications
256
+ - Gateway rules validation
257
+ - Hierarchical agent names
258
+ - Wildcard pattern validation
259
+ - Cross-validation between rules and servers
260
+ - Config path resolution
261
+ - File not found errors
262
+ - Invalid JSON handling
263
+
264
+ ## Usage Example
265
+
266
+ ```python
267
+ import os
268
+ from src.config import (
269
+ load_mcp_config,
270
+ load_gateway_rules,
271
+ validate_rules_against_servers,
272
+ get_config_path
273
+ )
274
+
275
+ # Set required environment variables
276
+ os.environ["BRAVE_API_KEY"] = "your_api_key"
277
+ os.environ["POSTGRES_URL"] = "postgresql://localhost/db"
278
+
279
+ # Get config paths
280
+ mcp_config_path = get_config_path(
281
+ "GATEWAY_MCP_CONFIG",
282
+ "./config/.mcp.json"
283
+ )
284
+ rules_path = get_config_path(
285
+ "GATEWAY_RULES",
286
+ ".mcp-gateway-rules.json"
287
+ )
288
+
289
+ # Load configurations
290
+ try:
291
+ mcp_config = load_mcp_config(mcp_config_path)
292
+ rules = load_gateway_rules(rules_path)
293
+
294
+ # Cross-validate
295
+ warnings = validate_rules_against_servers(rules, mcp_config)
296
+ if warnings:
297
+ for warning in warnings:
298
+ print(f"Warning: {warning}")
299
+
300
+ print(f"Loaded {len(mcp_config['mcpServers'])} servers")
301
+ print(f"Loaded {len(rules['agents'])} agent policies")
302
+
303
+ except FileNotFoundError as e:
304
+ print(f"Configuration file not found: {e}")
305
+ except ValueError as e:
306
+ print(f"Configuration validation error: {e}")
307
+ except Exception as e:
308
+ print(f"Unexpected error: {e}")
309
+ ```
310
+
311
+ ## Best Practices
312
+
313
+ 1. **Use `.env.example`**: Document all required environment variables in `.env.example`
314
+ 2. **Validate Early**: Run `validate_config.py` before starting the gateway
315
+ 3. **Cross-Validate**: Always run `validate_rules_against_servers()` to catch undefined server references
316
+ 4. **Secure Secrets**: Never commit actual API keys or credentials to version control
317
+ 5. **Clear Naming**: Use descriptive server and agent names
318
+ 6. **Wildcard Safety**: Use specific patterns (e.g., `get_*`) instead of `*` when possible
319
+ 7. **Deny-Before-Allow**: Place deny rules before allow rules for clarity (enforced at runtime)
320
+
321
+ ## Implementation Details
322
+
323
+ ### Environment Variable Substitution
324
+
325
+ The `_substitute_env_vars()` function recursively processes all strings in the configuration:
326
+
327
+ - **Pattern:** `${VARIABLE_NAME}`
328
+ - **Behavior:** Replaces with `os.environ["VARIABLE_NAME"]`
329
+ - **Error:** Raises `ValueError` if variable not set
330
+ - **Recursive:** Processes nested dicts and lists
331
+
332
+ ### Validation Order
333
+
334
+ 1. **File existence** - Check file exists
335
+ 2. **JSON parsing** - Parse JSON structure
336
+ 3. **Structure validation** - Validate top-level keys
337
+ 4. **Type validation** - Check all field types
338
+ 5. **Transport validation** - Validate stdio/HTTP requirements
339
+ 6. **Pattern validation** - Validate wildcards
340
+ 7. **Environment substitution** - Replace ${VAR} patterns
341
+ 8. **Cross-validation** - Validate references between configs
342
+
343
+ ### Path Handling
344
+
345
+ All file paths are:
346
+ 1. Expanded (`~/` → home directory)
347
+ 2. Resolved to absolute paths
348
+ 3. Validated for existence
349
+ 4. Reported in error messages with full paths
350
+
351
+ This ensures consistent behavior across different execution contexts and clear error reporting.
src/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Agent MCP Gateway - Core implementation package."""
src/audit.py ADDED
@@ -0,0 +1,94 @@
1
+ """Audit logging for Agent MCP Gateway."""
2
+
3
+ import json
4
+ import sys
5
+ import time
6
+ from datetime import datetime, timezone
7
+ from functools import wraps
8
+ from pathlib import Path
9
+ from typing import Any, Callable
10
+
11
+
12
+ class AuditLogger:
13
+ """Logs gateway operations for security auditing and debugging."""
14
+
15
+ def __init__(self, log_path: str = "~/.cache/agent-mcp-gateway/logs/audit.jsonl"):
16
+ """Initialize audit logger with log file path.
17
+
18
+ Args:
19
+ log_path: Path to audit log file (JSONL format)
20
+ """
21
+ self.log_path = Path(log_path).expanduser()
22
+ # Create log directory if it doesn't exist
23
+ self.log_path.parent.mkdir(parents=True, exist_ok=True)
24
+
25
+ def log(
26
+ self,
27
+ agent_id: str,
28
+ operation: str,
29
+ decision: str,
30
+ latency_ms: float,
31
+ metadata: dict[str, Any] | None = None
32
+ ):
33
+ """Log an audit entry to the log file.
34
+
35
+ Args:
36
+ agent_id: Agent making the request
37
+ operation: Operation name (list_servers, execute_tool, etc.)
38
+ decision: ALLOW, DENY, or ERROR
39
+ latency_ms: Operation latency in milliseconds
40
+ metadata: Additional context (server, tool, error, etc.)
41
+ """
42
+ entry = {
43
+ "timestamp": datetime.now(timezone.utc).isoformat(),
44
+ "agent_id": agent_id,
45
+ "operation": operation,
46
+ "decision": decision,
47
+ "latency_ms": round(latency_ms, 2),
48
+ "metadata": metadata or {}
49
+ }
50
+
51
+ try:
52
+ with open(self.log_path, 'a') as f:
53
+ f.write(json.dumps(entry) + "\n")
54
+ f.flush() # Ensure immediate write
55
+ except Exception as e:
56
+ # Log to stderr if file logging fails
57
+ # Don't raise - logging failures shouldn't crash the gateway
58
+ print(f"WARNING: Failed to write audit log: {e}", file=sys.stderr)
59
+
60
+
61
+ def audit_operation(operation: str, audit_logger: AuditLogger):
62
+ """Decorator to automatically audit an operation.
63
+
64
+ Args:
65
+ operation: Operation name
66
+ audit_logger: AuditLogger instance
67
+
68
+ Returns:
69
+ Decorated function that logs operation timing and outcome
70
+ """
71
+ def decorator(func: Callable):
72
+ @wraps(func)
73
+ async def wrapper(*args, **kwargs):
74
+ start_time = time.time()
75
+ agent_id = kwargs.get("agent_id", "unknown")
76
+
77
+ try:
78
+ result = await func(*args, **kwargs)
79
+ latency_ms = (time.time() - start_time) * 1000
80
+ audit_logger.log(agent_id, operation, "ALLOW", latency_ms)
81
+ return result
82
+ except Exception as e:
83
+ latency_ms = (time.time() - start_time) * 1000
84
+ audit_logger.log(
85
+ agent_id,
86
+ operation,
87
+ "ERROR",
88
+ latency_ms,
89
+ metadata={"error": str(e)}
90
+ )
91
+ raise
92
+
93
+ return wrapper
94
+ return decorator
@@ -0,0 +1,59 @@
1
+ {
2
+ "agents": {
3
+ "default": {
4
+ "deny": {
5
+ "servers": ["*"]
6
+ }
7
+ },
8
+ "researcher": {
9
+ "allow": {
10
+ "servers": ["brave-search", "context7"],
11
+ "tools": {
12
+ "brave-search": ["brave_web_search"]
13
+ }
14
+ }
15
+ },
16
+ "backend": {
17
+ "allow": {
18
+ "servers": ["postgres", "laravel-boost"],
19
+ "tools": {
20
+ "postgres": ["query", "list_tables", "list_schemas"],
21
+ "laravel-boost": ["get_*", "list_*", "read_*", "database_*", "search_*"]
22
+ }
23
+ },
24
+ "deny": {
25
+ "tools": {
26
+ "postgres": ["drop_*", "delete_*"],
27
+ "laravel-boost": ["database_query", "tinker"]
28
+ }
29
+ }
30
+ },
31
+ "admin": {
32
+ "allow": {
33
+ "servers": ["*"],
34
+ "tools": {
35
+ "brave-search": ["brave_web_search"]
36
+ }
37
+ },
38
+ "deny": {
39
+ "servers": ["notion"],
40
+ "tools": {
41
+ "playwright": ["browser_type"]
42
+ }
43
+ }
44
+ },
45
+ "claude-desktop": {
46
+ "allow": {
47
+ "servers": ["context7", "brave-search", "notion", "playwright"]
48
+ },
49
+ "deny": {
50
+ "tools": {
51
+ "playwright": ["browser_type", "browser_close_all", "launch_*"]
52
+ }
53
+ }
54
+ }
55
+ },
56
+ "defaults": {
57
+ "deny_on_missing_agent": false
58
+ }
59
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "mcpServers": {
3
+ "brave-search": {
4
+ "description": "Web search via Brave Search API",
5
+ "command": "npx",
6
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
7
+ "env": {
8
+ "BRAVE_API_KEY": "${BRAVE_API_KEY}"
9
+ }
10
+ },
11
+ "postgres": {
12
+ "description": "PostgreSQL database access and query execution",
13
+ "command": "uvx",
14
+ "args": ["mcp-server-postgres"],
15
+ "env": {
16
+ "POSTGRES_URL": "${POSTGRES_URL}"
17
+ }
18
+ },
19
+ "filesystem": {
20
+ "description": "Read and write files in /workspace directory",
21
+ "command": "npx",
22
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
23
+ },
24
+ "notion": {
25
+ "description": "Access and manage Notion workspaces",
26
+ "url": "https://mcp.notion.com/mcp",
27
+ "transport": "http"
28
+ }
29
+ }
30
+ }