Skip to content

Commit 7b0a663

Browse files
authored
Merge pull request #6 from barlanyado/main
mcp security scanner
2 parents dc3396d + 1d541cd commit 7b0a663

19 files changed

+2984
-615
lines changed

README.md

Lines changed: 102 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
1-
# MCP Gateway
21

3-
![Hugging Face Token Masking Example](docs/MCP_Flow.png)
2+
<div align="center">
3+
<a href="https://pypi.org/project/mcp-gateway/">
4+
<img src="https://img.shields.io/pypi/v/mcp-gateway.svg?color=blue" alt="PyPI version">
5+
</a>
6+
<a href="https://pypi.org/project/mcp-gateway/">
7+
<img src="https://img.shields.io/pypi/pyversions/mcp-gateway.svg" alt="Python Versions">
8+
</a>
9+
<a href="./LICENSE">
10+
<img src="https://img.shields.io/github/license/lasso-security/mcp-gateway" alt="License">
11+
</a>
12+
13+
# MCP Gateway
14+
15+
</div>
16+
17+
# Overview
18+
![](docs/MCP_Flow.png)
419

520
MCP Gateway is an advanced intermediary solution for Model Context Protocol (MCP) servers that centralizes and enhances your AI infrastructure.
621

722
MCP Gateway acts as an intermediary between LLMs and other MCP servers. It:
823

9-
1. Reads server configurations from a `mcp.json` file located in your root directory.
10-
2. Manages the lifecycle of configured MCP servers.
11-
3. Intercepts requests and responses to sanitize sensitive information.
12-
4. Provides a unified interface for discovering and interacting with all proxied MCPs.
24+
1. 📄 Reads server configurations from a `mcp.json` file located in your root directory.
25+
2. ⚙️ Manages the lifecycle of configured MCP servers.
26+
3. 🛡️ Intercepts requests and responses to sanitize sensitive information.
27+
4. 🔗 Provides a unified interface for discovering and interacting with all proxied MCPs.
28+
5. 🔒 **Security Scanner** - Analyzes server reputation and security risks before loading MCP servers.
1329

1430
## Installation
1531

@@ -293,6 +309,7 @@ The Lasso guardrail checks content through Lasso's API for security violations b
293309

294310
Read more on our website 👉 [Lasso Security](https://www.lasso.security/).
295311

312+
296313
## Tracing
297314

298315
### Xetrack
@@ -398,6 +415,82 @@ D SELECT server_name,capability_name,path,content_text FROM db.events LIMIT 1;
398415
399416
Of course you can use another MCP server to query the sqlite database 😊
400417
418+
# Scanner
419+
420+
The Security Scanner analyzes MCP servers for potential security risks before loading, providing an additional layer of protection through reputation analysis and tool description scanning.
421+
422+
```bash
423+
mcp-gateway --scan -p basic
424+
```
425+
426+
**Features:**
427+
- 🔍 **Reputation Analysis** - Evaluates server reputation using marketplace (Smithery, NPM) and GitHub data
428+
- 🛡️ **Tool Description Scanning** - Detects hidden instructions, sensitive file patterns, and malicious actions
429+
- ⚡ **Automatic Blocking** - Blocks risky MCPs based on reputation scores (threshold: 30) and security analysis
430+
- 📝 **Configuration Updates** - Automatically updates your MCP configuration file with scan results
431+
432+
## Quickstart
433+
Initial configuration:
434+
```json
435+
{
436+
"mcpServers": {
437+
"mcp-gateway": {
438+
"command": "mcp-gateway",
439+
"args": [
440+
"--mcp-json-path",
441+
"~/.cursor/mcp.json",
442+
"--scan"
443+
],
444+
"servers": {
445+
"filesystem": {
446+
"command": "npx",
447+
"args": [
448+
"-y",
449+
"@modelcontextprotocol/server-filesystem",
450+
"."
451+
]
452+
}
453+
}
454+
}
455+
}
456+
}
457+
```
458+
After the first run, the scanner will analyze all configured MCP servers and add a `blocked` status to your configuration:
459+
```json
460+
{
461+
"mcpServers": {
462+
"mcp-gateway": {
463+
"command": "mcp-gateway",
464+
"args": [
465+
"--mcp-json-path",
466+
"~/.cursor/mcp.json",
467+
"--scan"
468+
],
469+
"servers": {
470+
"filesystem": {
471+
"command": "npx",
472+
"args": [
473+
"-y",
474+
"@modelcontextprotocol/server-filesystem",
475+
"."
476+
],
477+
"blocked": "passed"
478+
}
479+
}
480+
}
481+
}
482+
}
483+
```
484+
**Status Values:**
485+
- `"passed"` - Server passed all security checks and is safe to use
486+
- `"blocked"` - Server failed security checks and will be blocked from loading
487+
- `"skipped"` - Server scanning was skipped (manual override)
488+
- `null` - Server not yet scanned or previously blocked server now considered safe
489+
490+
> **Note:** You can manually change a blocked server to `"skipped"` if you're confident it's safe.
491+
492+
493+
401494
## How It Works
402495
Your agent interacts directly with our MCP Gateway, which functions as a central router and management system. Each underlying MCP is individually wrapped and managed.
403496
@@ -413,6 +506,9 @@ Key Features
413506
* Includes intelligent risk assessment with MCP risk scoring.
414507
* Delivers real-time status monitoring and performance metrics.
415508
509+
**Security Scanner**
510+
* Analyzes MCP server reputation and tool descriptions for security risks before loading.
511+
416512
**Advanced Tracking**
417513
* Maintains detailed logs of all requests and responses for each guardrail.
418514
* Offers cost evaluation tools for MCPs requiring paid tokens.

mcp_gateway/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
This package provides functionality to manage and route requests to multiple MCP servers.
55
"""
66

7-
__version__ = "0.1.0"
7+
__version__ = "1.1.0"

mcp_gateway/config.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import logging
33
import os
44
from pathlib import Path
5-
from typing import Dict, Any
5+
from typing import Dict, Any, List, Tuple
6+
from mcp import types
67

78
CONFIG_FILE_NAME = "mcp.json"
89

@@ -11,6 +12,10 @@
1112
# logging.basicConfig(level=logging.INFO) # Removed basicConfig here
1213

1314

15+
class Constants:
16+
SERVERS = "servers"
17+
18+
1419
def find_config_file(mcp_json_path: str) -> Path | None:
1520
"""Uses the provided mcp_json_path to locate the configuration file.
1621
@@ -90,21 +95,21 @@ def load_servers_config_from_path(config_path: Path) -> Dict[str, Any]:
9095
return {}
9196

9297
# 3. Extract the nested 'servers' key from the gateway's config
93-
nested_servers_config = gateway_config.get("servers", {})
98+
nested_servers_config = gateway_config.get(Constants.SERVERS, {})
9499
if not isinstance(nested_servers_config, dict):
95100
logger.warning(
96-
f"Nested 'servers' key within '{gateway_config_key}' config in {config_path} is not a dictionary. Treating as empty."
101+
f"Nested '{Constants.SERVERS}' key within '{gateway_config_key}' config in {config_path} is not a dictionary. Treating as empty."
97102
)
98103
return {} # Return empty dict if nested 'servers' isn't a dict
99104

100105
if not nested_servers_config:
101106
logger.warning(
102-
f"Nested 'servers' key within '{gateway_config_key}' config in {config_path} is missing or empty. No proxied servers will be configured."
107+
f"Nested '{Constants.SERVERS}' key within '{gateway_config_key}' config in {config_path} is missing or empty. No proxied servers will be configured."
103108
)
104109
# Fall through to return the (potentially empty) nested_servers_config
105110

106111
logger.info(
107-
f"Successfully extracted nested 'servers' config for proxied servers from {config_path}."
112+
f"Successfully extracted nested '{Constants.SERVERS}' config for proxied servers from {config_path}."
108113
)
109114
# Return the extracted dict directly
110115
return nested_servers_config
@@ -161,3 +166,31 @@ def load_config(mcp_json_path: str) -> Dict[str, Any]:
161166
)
162167
logger.warning("Using empty configuration for proxied servers.")
163168
return {} # Return empty dict
169+
170+
171+
def get_tool_params_description(tool: types.Tool) -> List[Tuple[str, Any, str]]:
172+
param_signatures = []
173+
174+
# Tool has inputSchema (JSON Schema) instead of arguments
175+
if hasattr(tool, "inputSchema") and tool.inputSchema:
176+
# Try to extract properties from JSON Schema
177+
properties = tool.inputSchema.get("properties", {})
178+
for param_name, param_schema in properties.items():
179+
param_type = Any # Default type
180+
param_description = param_schema.get("description", "")
181+
182+
# Map JSON Schema types to Python types
183+
json_type = param_schema.get("type")
184+
if json_type:
185+
type_mapping = {
186+
"string": str,
187+
"integer": int,
188+
"boolean": bool,
189+
"number": float,
190+
"object": Dict[str, Any],
191+
"array": List[Any],
192+
}
193+
param_type = type_mapping.get(json_type, Any)
194+
195+
param_signatures.append((param_name, param_type, param_description))
196+
return param_signatures

0 commit comments

Comments
 (0)