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.
- agent_mcp_gateway-0.2.1.dist-info/METADATA +1330 -0
- agent_mcp_gateway-0.2.1.dist-info/RECORD +18 -0
- agent_mcp_gateway-0.2.1.dist-info/WHEEL +4 -0
- agent_mcp_gateway-0.2.1.dist-info/entry_points.txt +2 -0
- agent_mcp_gateway-0.2.1.dist-info/licenses/LICENSE +21 -0
- src/CONFIG_README.md +351 -0
- src/__init__.py +1 -0
- src/audit.py +94 -0
- src/config/.mcp-gateway-rules.json.example +59 -0
- src/config/.mcp.json.example +30 -0
- src/config.py +849 -0
- src/config_watcher.py +296 -0
- src/gateway.py +547 -0
- src/main.py +570 -0
- src/metrics.py +299 -0
- src/middleware.py +166 -0
- src/policy.py +500 -0
- src/proxy.py +649 -0
|
@@ -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,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
|
+
}
|