Skip to main content
Glama
vinod-vk17

API Catalog Recommender MCP Server

by vinod-vk17
README.md
# MCP-Powered API Catalog Recommender

An agentic API discovery system that combines **semantic vector search** over an OpenAPI catalog with a **LangGraph orchestrator** and **MCP (Model Context Protocol)** tools. Given a natural-language intent (e.g. *"create a chat completion"* or *"charge a customer $50"*), the agent retrieves the best-matching endpoints and returns a grounded, technical recommendation.

---

## Architecture

The system uses a **two-phase, decoupled design**: expensive embedding work happens offline; runtime queries stay fast with at most **one NIM call per search**.

```mermaid
flowchart TB
    subgraph phase1 [Phase 1 - Offline Indexing]
        SPECS[OpenAPI specs in data/specs]
        BUILD[scripts/build_index.py]
        NIM_EMB[NVIDIA NIM nv-embedqa-e5-v5]
        FAISS[(faiss.index)]
        META[(metadata.json)]
        SPECS --> BUILD
        BUILD --> NIM_EMB
        NIM_EMB --> FAISS
        BUILD --> META
    end

    subgraph phase2 [Phase 2 - Runtime Serving]
        USER[User or Client]
        CLI[CLI src/mcp_agent.py]
        API[FastAPI src/main.py]
        AGENT[LangGraph MCPCatalogAgent]
        VERTEX[Vertex AI Qwen 2.5 7B primary]
        NIM_LLM[NVIDIA NIM Llama 3.1 8B fallback]
        MCP[FastMCP api_catalog_mcp.py]
        SEARCH[search_api_catalog]
        DETAILS[get_endpoint_details]

        USER --> CLI
        USER --> API
        CLI --> AGENT
        API --> AGENT
        AGENT --> VERTEX
        AGENT -.-> NIM_LLM
        AGENT --> MCP
        MCP --> AGENT
        MCP --> SEARCH
        MCP --> DETAILS
        SEARCH --> FAISS
        SEARCH --> META
        DETAILS --> META
    end
```

### Request flow

1. User sends a natural-language query via CLI or `POST /query`.
2. LangGraph agent calls `search_api_catalog` → **1 NVIDIA NIM embedding call** + local FAISS top-5 search.
3. Agent calls `get_endpoint_details` for the best match(es) → **0 NIM calls** (pure JSON lookup).
4. Primary LLM (Vertex AI Qwen 2.5 7B) synthesizes a Markdown recommendation; on failure, falls back to NVIDIA NIM Llama 3.1 8B.

---

## Design Choices

| Area | Choice | Rationale |
|------|--------|-----------|
| **Retrieval** | FAISS `IndexFlatIP` on L2-normalized vectors | Exact cosine similarity via inner product; fast enough for ~20–10k endpoints on CPU |
| **Embeddings** | NVIDIA NIM `nvidia/nv-embedqa-e5-v5` | Separate `passage` (index) vs `query` (search) input types for better retrieval quality |
| **Protocol** | FastMCP stdio server | Standard MCP tool interface; agent discovers tools at runtime via `langchain-mcp-adapters` |
| **Orchestration** | LangGraph state machine | Bounded tool-calling loop (max 6 iterations) with explicit agent → action → agent edges |
| **Primary LLM** | Vertex AI Qwen 2.5 7B (`:rawPredict`) | Enterprise-hosted inference; OpenAI-compatible client with URL rewrite hook |
| **Fallback LLM** | NVIDIA NIM `meta/llama-3.1-8b-instruct` | Resilience when Vertex endpoint is unavailable |
| **Serving** | FastAPI + Uvicorn | REST `/query` and `/health` for integration; Swagger at `/docs` |
| **Index build** | Offline batch job | Avoids re-embedding catalog on every server start; predictable startup latency |

---

## Constraints & Limitations

- **Pre-built index required** — `data/faiss.index` and `data/metadata.json` must exist before starting the MCP server or agent. Run the indexer first.
- **Catalog scope** — Currently indexes OpenAPI specs under `data/specs/` only (OpenAI + Stripe in the default dataset).
- **Top-K = 5** — `search_api_catalog` returns at most 5 endpoints per query (`TOP_K` in `src/api_catalog_mcp.py`).
- **Loop guard** — Agent terminates after 6 LLM iterations to prevent infinite tool loops (`MAX_LOOP_ITERATIONS` in `src/mcp_agent.py`).
- **Vertex AI auth** — Primary LLM requires Google Application Default Credentials (`gcloud auth application-default login`).
- **Windows file locks** — Rebuilding the FAISS index while the FastAPI server is running may fail with `PermissionError`; stop the server first.
- **NIM dependency at search time** — Each semantic search makes exactly one embedding API call; detail lookups are free.

---

## Dataset

### Source specs (`data/specs/`)

| File | API | Endpoints |
|------|-----|-----------|
| `openai_openapi.json` | OpenAI API | 10 |
| `stripe_openapi.json` | Stripe API | 10 |
| **Total** | **2 APIs** | **20 endpoints** |

### Derived artifacts (`data/`)

| File | Description |
|------|-------------|
| `faiss.index` | Binary FAISS `IndexFlatIP` — one normalized vector per endpoint |
| `metadata.json` | Full endpoint records: `api_name`, `path`, `method`, `summary`, `description`, `parameters`, `requestBody`, `responses` |
| `api_catalog.json` | Supplementary sample catalog (Ford vehicle/EV APIs) — reference data, not indexed by default |

### Embedding input format

Each indexed endpoint is embedded as:

```text
{api_name} {METHOD} {path}: {summary}
```

Example: `Openai API POST /v1/chat/completions: Create a chat completion`

### Adding new APIs

1. Drop an OpenAPI 3.0 JSON file into `data/specs/` (e.g. `twilio_openapi.json`).
2. Re-run the index builder (see [Quick Start](#quick-start)).
3. Restart the MCP server / FastAPI service to load the new index.

---

## Project Structure

```text
mcp-catalog-agent/
├── src/
│   ├── api_catalog_mcp.py   # FastMCP server — search + detail tools
│   ├── mcp_agent.py         # LangGraph agent + CLI entry point
│   └── main.py              # FastAPI REST service
├── scripts/
│   ├── build_index.py       # Offline FAISS index builder
│   └── parse_output.ps1     # Saves base64 index output to data/ (Windows helper)
├── data/
│   ├── specs/               # OpenAPI 3.0 source specs
│   ├── faiss.index          # Generated vector index
│   └── metadata.json        # Generated endpoint metadata
├── run_test_sequence.py     # Spins up server, hits /health + /query, tears down
├── query_service.py         # HTTP smoke test against a running server
├── requirements.txt
├── TESTING.md               # Extended troubleshooting guide
└── .env.example
```

---

## Quick Start

### 1. Clone and install

```powershell
cd mcp-catalog-agent
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txt
```

### 2. Configure environment

```powershell
Copy-Item .env.example .env
# Edit .env with your NVIDIA_API_KEY and VERTEX_ENDPOINT_URL
```

| Variable | Required | Purpose |
|----------|----------|---------|
| `NVIDIA_API_KEY` | Yes | Embeddings + LLM fallback |
| `VERTEX_ENDPOINT_URL` | Yes | Primary Qwen 2.5 7B endpoint |
| `EMBEDDING_MODEL` | No | Default: `nvidia/nv-embedqa-e5-v5` |
| `NVIDIA_BASE_URL` | No | Default: `https://integrate.api.nvidia.com/v1` |
| `LANGCHAIN_*` | No | LangSmith tracing |

### 3. Build the vector index

```powershell
python scripts/build_index.py > build_output_utf8.txt
.\scripts\parse_output.ps1
```

Verify `data/faiss.index` and `data/metadata.json` were created.

### 4. Run the CLI agent

```powershell
python src/mcp_agent.py "How do I create a chat completion using OpenAI?"
```

### 5. Run the FastAPI service

```powershell
python -m uvicorn src.main:app --host 127.0.0.1 --port 8000
```

Open [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) for interactive API docs.

---

## Testing Examples

### CLI queries

```powershell
# OpenAI — chat completions
python src/mcp_agent.py "How do I create a chat completion using the OpenAI API?"

# Stripe — customers and charges
python src/mcp_agent.py "I need to list customers and create a $50 charge with Stripe."

# Stripe — invoices
python src/mcp_agent.py "How do I retrieve a customer invoice from Stripe?"
```

### REST API

**Health check**

```powershell
Invoke-RestMethod -Uri "http://127.0.0.1:8000/health" -Method Get
```

Expected response shape:

```json
{
  "status": "healthy",
  "agent_initialized": true,
  "tools_count": 2,
  "tools": ["search_api_catalog", "get_endpoint_details"]
}
```

**Query**

```powershell
$body = @{ query = "Find me a chat completion API" } | ConvertTo-Json
Invoke-RestMethod -Uri "http://127.0.0.1:8000/query" -Method Post -Body $body -ContentType "application/json"
```

**curl**

```bash
curl -X POST http://127.0.0.1:8000/query \
  -H "Content-Type: application/json" \
  -d '{"query": "How do I create a charge in Stripe?"}'
```

### Automated smoke test

With the server already running:

```powershell
python query_service.py
```

Or start server, test, and stop automatically:

```powershell
python run_test_sequence.py
```

### Verify NVIDIA NIM connectivity

```powershell
python test_nvidia.py
```

---

## MCP Tools

| Tool | NIM calls | Description |
|------|-----------|-------------|
| `search_api_catalog` | 1 per invocation | Semantic search; returns top matches with `api_name`, `path`, `method`, `summary`, `score` |
| `get_endpoint_details` | 0 | Full endpoint spec lookup by exact `api_name` + `path` |

The agent system prompt enforces: **search first → fetch details → synthesize recommendation**.

---

## Observability

When `LANGCHAIN_TRACING_V2=true`, traces appear in [LangSmith](https://smith.langchain.com/) under project `mcp-api-catalog-recommender`. Inspect the trace tree to verify tool-call order and LLM fallback behavior.

---

## Troubleshooting

See [TESTING.md](TESTING.md) for Windows-specific issues (`pywintypes`, port conflicts, FAISS file locks).

---

## License

MIT (OpenAPI source specs retain their original licenses.)

Maintenance

ActivitySlowing
ResponsivenessNo issues