Skip to main content
Glama
aldoleiva1

Cisco PSIRT OpenVuln MCP Server

by aldoleiva1
README.md
# Cisco PSIRT OpenVuln MCP Server

A production-quality [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for querying Cisco security advisories via the PSIRT OpenVuln API v2. Built with Python and FastMCP, it enables LLM-powered interfaces like Claude Desktop and Kiro to search, filter, and analyze Cisco vulnerability data.

## Features

- **15 MCP tools** covering all Cisco PSIRT OpenVuln API v2 endpoints
- **OAuth2 authentication** with automatic token caching and refresh (client_credentials flow)
- **Multi-tier rate limiting** — 5 calls/sec, 30 calls/min, 5,000 calls/day (client-side enforcement)
- **Server-side 429 retry** — automatic retry with Retry-After header support (up to 3 attempts)
- **Input validation** on all parameters with descriptive error messages
- **Pagination support** across all list endpoints (page_index, page_size)
- **LLM-optimized responses** — consistent structured envelope with summary truncation
- **Transport-agnostic** — supports stdio (default) and SSE transports
- **Structured error handling** — classified errors with user-friendly messages

## Prerequisites

- **Python 3.10+**
- **Cisco API Console credentials** (client_id and client_secret) — see [Getting Cisco API Credentials](#getting-cisco-api-credentials)

## Installation

1. **Clone the repository:**

   ```bash
   git clone <repository-url>
   cd openvuln-mcp-server
   ```

2. **Create a virtual environment:**

   ```bash
   python -m venv venv
   source venv/bin/activate  # On Windows: venv\Scripts\activate
   ```

3. **Install dependencies:**

   ```bash
   pip install -r requirements.txt
   ```

4. **Configure credentials:**

   ```bash
   cp .env.example .env
   ```

   Edit `.env` and fill in your Cisco API credentials:

   ```dotenv
   CISCO_CLIENT_ID=your_client_id_here
   CISCO_CLIENT_SECRET=your_client_secret_here
   ```

## Getting Cisco API Credentials

1. Go to the [Cisco API Console](https://apidocs-prod.cisco.com/) and sign in (or create an account)
2. Register a new application
3. Enable the **"Cisco PSIRT openVuln API"** for your application
4. Copy the generated `client_id` and `client_secret`
5. Add them to your `.env` file or pass them as environment variables

Each user's credentials are independent — rate limits are tracked per client_id on Cisco's side.

## Usage

### stdio mode (default)

Standard transport for local MCP integrations (Claude Desktop, Kiro):

```bash
python main.py
```

### SSE mode

HTTP-based Server-Sent Events transport for web or remote integrations:

```bash
python main.py --transport sse --port 8080
```

## MCP Client Configuration

### Claude Desktop / Kiro (stdio)

Add to your MCP client configuration file:

```json
{
  "mcpServers": {
    "cisco-openvuln": {
      "command": "python",
      "args": ["main.py"],
      "cwd": "/path/to/openvuln-mcp-server",
      "env": {
        "CISCO_CLIENT_ID": "your_client_id",
        "CISCO_CLIENT_SECRET": "your_client_secret"
      }
    }
  }
}
```

### SSE client

```json
{
  "mcpServers": {
    "cisco-openvuln": {
      "url": "http://localhost:8080/sse"
    }
  }
}
```

## Available Tools

| # | Tool | Description | Key Parameters |
|---|------|-------------|----------------|
| 1 | `get_all_advisories` | Retrieve all published advisories with pagination | `page_index`, `page_size` |
| 2 | `get_advisory_by_id` | Look up a specific advisory by Cisco advisory ID | `advisory_id` (max 100 chars) |
| 3 | `get_advisory_by_cve` | Find advisories by CVE identifier | `cve_id` (CVE-YYYY-NNNNN format) |
| 4 | `get_advisory_by_bug_id` | Find advisories by Cisco Bug ID | `bug_id` (CSCxxNNNNN format) |
| 5 | `get_latest_advisories` | Get the N most recently published advisories | `number` (1–100, default 5) |
| 6 | `get_advisories_by_severity` | Filter advisories by severity level | `severity` (critical/high/medium/low/informational) |
| 7 | `get_advisories_by_severity_first_published` | Filter by severity and first-published date range | `severity`, `start_date`, `end_date` |
| 8 | `get_advisories_by_severity_last_published` | Filter by severity and last-updated date range | `severity`, `start_date`, `end_date` |
| 9 | `get_advisories_by_first_published` | Find advisories first published in a date range | `start_date`, `end_date` (YYYY-MM-DD) |
| 10 | `get_advisories_by_last_published` | Find advisories last updated in a date range | `start_date`, `end_date` (YYYY-MM-DD) |
| 11 | `get_advisories_by_product` | Search advisories by product name | `product_name` |
| 12 | `get_advisories_by_year` | Get advisories published in a specific year | `year` (1995–current) |
| 13 | `get_advisories_by_os_version` | Find advisories for a specific OS type and version | `os_type`, `version`, `platform_alias` (optional) |
| 14 | `get_os_version_data` | Get available OS version metadata | `os_type` |
| 15 | `get_platform_aliases` | List platform aliases for an OS type | `os_type` (nxos/asa/ftd/fxos only) |

All list-returning tools support `page_index` (1–100) and `page_size` (1–100) pagination parameters.

## Running Tests

Run the full test suite:

```bash
pytest
```

Run with coverage:

```bash
pytest --cov=src --cov-report=term-missing
```

Run a specific test file:

```bash
pytest tests/test_validators.py
```

Run property-based tests (Hypothesis):

```bash
pytest tests/ -k "property"
```

## Rate Limits

The server enforces client-side rate limiting to stay within Cisco API quotas:

| Tier | Limit | Enforcement |
|------|-------|-------------|
| Per-second | 5 calls/sec | Minimum 200ms between requests |
| Per-minute | 30 calls/min | Rolling 60-second sliding window |
| Per-day | 5,000 calls/day | Calendar day counter, resets at 00:00 UTC |

When a rate limit is hit:
- **Per-second / per-minute**: The server automatically sleeps until the next request is permitted
- **Per-day**: Returns an error indicating the daily limit is exhausted, with seconds until reset
- **Server-side 429**: Retries up to 3 times using the `Retry-After` header (default 60s if absent)

## Project Structure

```
openvuln-mcp-server/
├── main.py                    # Entry point: loads env, parses args, starts server
├── src/
│   ├── __init__.py
│   ├── server.py              # FastMCP server and 15 tool registrations
│   ├── oauth2_client.py       # OAuth2 client_credentials with token caching
│   ├── rate_limiter.py        # Multi-tier rate limiter
│   ├── api_client.py          # HTTP client with auth, rate limiting, retry
│   ├── validators.py          # Input validation functions
│   ├── response_formatter.py  # LLM-optimized response formatting
│   ├── constants.py           # URLs, limits, validation rules
│   └── exceptions.py          # Custom exception hierarchy
├── tests/
│   ├── test_validators.py
│   ├── test_oauth2_client.py
│   ├── test_rate_limiter.py
│   ├── test_api_client.py
│   ├── test_response_formatter.py
│   └── test_tools.py
├── .env.example               # Credentials template
├── requirements.txt           # Python dependencies
└── README.md
```

## License

This project is licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).