Skip to main content
Glama
RanneG

linkup-mcp

by RanneG
README.md
# Cursor Linkup MCP Server

Custom MCP (Model Context Protocol) server for Cursor IDE with:
- 🌐 **Web Search** - Deep web searches using [Linkup API](https://www.linkup.so/)
- πŸ“š **RAG (Retrieval Augmented Generation)** - Query documents using LlamaIndex with Ollama

## ✨ Key Features

- βœ… **Local AI** - Uses Ollama (llama3.2) for complete privacy
- βœ… **Zero API Costs** - RAG tool is completely free (uses local models)
- βœ… **Source Citations** - Know where answers come from
- βœ… **Multiple Document Types** - Supports PDF, DOCX, MD, TXT, and more
- βœ… **Cursor Integration** - Works seamlessly in Cursor IDE

## πŸ“‹ Prerequisites

- **Python 3.12+**
- **[uv](https://github.com/astral-sh/uv)** package manager
- **[Ollama](https://ollama.ai/)** installed locally with llama3.2 model
- **Linkup API key** (optional, only for web search)

## πŸš€ Quick Start

### 1. Clone & Install Dependencies

```bash
git clone https://github.com/RanneG/linkup_mcp.git
cd linkup_mcp
uv sync
```

**Default install** (`uv sync` / `pip install -e .`) is **Cursor MCP + RAG** only (lighter venv). For **`stitch_rag_bridge.py`**, face/OAuth/Gmail, and server-side voice STT, add **`--extra stitch-bridge`**. For **ElevenLabs** voice/music asset generation (`elevenlabs-gen` CLI), add **`--extra elevenlabs`** β€” see **[docs/elevenlabs/README.md](docs/elevenlabs/README.md)**.

### 2. Install Ollama & Model

```bash
# Download from https://ollama.ai/download
# Then pull the model:
ollama pull llama3.2
```

### 3. Configure Environment (Optional)

Create a `.env` file for web search (RAG works without API keys):

```bash
LINKUP_API_KEY=your_linkup_api_key  # Optional, for web_search tool
```

### 4. Configure Cursor

Add to `~/.cursor/mcp.json` (or `C:\Users\<username>\.cursor\mcp.json` on Windows):

```json
{
  "mcpServers": {
    "linkup-server": {
      "command": "C:\\Users\\YOUR_USERNAME\\AppData\\Local\\Microsoft\\WindowsApps\\python.exe",
      "args": [
        "-m", "uv", "run",
        "--directory", "C:\\path\\to\\linkup_mcp",
        "python", "server.py"
      ]
    }
  }
}
```

**Replace** `YOUR_USERNAME` and path with your actual values.

### 5. Free local dev ports (optional)

If **8765** (Stitch bridge / bundled UI), **1420** (Tauri), or **5173** (Vite) are stuck after a crash, run **`Close-DevPorts.bat`** at the repo root (or `.\scripts\Close-StitchDevPorts.ps1`). Use **`-DryRun`** to list listeners without killing. This stops **processes listening on those ports**, not β€œlocalhost” itself.

### 6. Restart Cursor & Use!

In Cursor's chat:
- **"Use the rag tool to tell me about [topic]"**
- **"Use the rag_stitch tool to get a UI-ready answer payload"**
- **"Search the web for [query]"** (requires Linkup API key)
- **Local Whisper (no Linkup):** `whisper_stt_status` then `transcribe_wav_file` with a path to a `.wav` file. Requires `pip install -e ".[stitch-whisper]"` and a Cursor MCP restart.

### 7. Voice-to-prompt hotkey tool (offline)

Use `voice_prompt_tool.py` for one-shot dictation directly into your coding flow:

```bash
uv sync --extra stitch-whisper --extra voice-prompt
python voice_prompt_tool.py --hotkey ctrl+shift+v
```

What it does:
- global start/stop hotkey for microphone capture
- local faster-whisper transcription (offline)
- file reference extraction (`auth.ts` -> `@auth.ts`)
- prompt envelope copied to clipboard:
  - `[FILE REFERENCE: ...]`
  - `[TASK: ...]`

Optional direct paste after copy:

```bash
python voice_prompt_tool.py --autopaste
```

## πŸ“š Using the RAG Tool

Add documents to the `data/` folder:

```
data/
β”œβ”€β”€ document1.pdf
β”œβ”€β”€ notes.md
└── research/
    └── paper.pdf
```

Supported: PDF, DOCX, TXT, MD, HTML, and more.

### Stitch-style response shape

The `rag` tool now returns a JSON string with:
- `answer`: final synthesized answer text
- `confidence`: `low`, `medium`, or `high`
- `fallback`: `true` when evidence is weak/insufficient
- `sources`: ranked source snippets (`source_id`, `score`, `snippet`)

The `rag_stitch` tool returns a UI-oriented JSON string:
- `state`: `answered` or `fallback`
- `answer`
- `confidence` (`low`, `medium`, or `high`)
- `source_cards` (empty when `state` is `fallback` so the UI stays clean)
- `show_sources` (`false` on fallback)
- `debug_retrieval_cards` (only when `STITCH_RAG_DEBUG=1` and fallback β€” raw top chunks for debugging)

### Repository split (Stitch production)

The **Stitch desktop app** (React + Tauri) is **migrating to** **[RanneG/stitch-app](https://github.com/RanneG/stitch-app)**; **linkup_mcp** remains the **MCP server** and **HTTP bridge** for local RAG, OAuth, subscriptions, face, and in-app help. Cutover checklist and file inventory: **[docs/stitch/MIGRATION.md](docs/stitch/MIGRATION.md)** (see **[docs/stitch/README.md](docs/stitch/README.md)**).

### `stitch-api-types` (TypeScript)

NPM workspace **`packages/stitch-api-types`** publishes `.d.ts` for **`POST /api/rag/stitch`**, **`POST /api/rag/stitch-help`**, **`GET /api/health`**, and related payloads. Build from repo root: **`npm run build:stitch-api-types`**. **stitch-app** can depend on it with a `file:` path (see **stitch-app** `docs/BACKEND.md`).

### Stitch HTTP bridge (for the Stitch desktop app)

Run a small Flask server that exposes the same payload as `rag_stitch`, plus optional **local face verification** (`/api/face/*`, DeepFace + OpenCV liveness β€” see `face_verification/`). Requires **`stitch-bridge`** extras:

```bash
uv sync --extra stitch-bridge
.\.venv\Scripts\python.exe stitch_rag_bridge.py
```

Then point the Stitch app’s Vite dev proxy at `http://127.0.0.1:8765` (see [stitch-app docs/BACKEND.md](https://github.com/RanneG/stitch-app/blob/main/docs/BACKEND.md) or [integrations/stitch/README.md](integrations/stitch/README.md); proxy `/api` for RAG, face, auth, subscriptions, and help routes).

**Who needs what:** Anyone can run the **Stitch UI** from **[stitch-app](https://github.com/RanneG/stitch-app)** with Node (see [docs/RUNNING.md](https://github.com/RanneG/stitch-app/blob/main/docs/RUNNING.md)). **linkup_mcp** is required for **`/api/*`** backend capabilities (auth, data, RAG, face, server-backed Help).

### Develop Stitch UI from this repo (optional)

Root scripts **`npm run dev:browser`**, **`npm run dev:desktop`**, **`npm run build:stitch-web`**, **`npm run build:stitch-app`** use **`scripts/run-stitch-ui.mjs`**, which resolves **`STITCH_APP_ROOT`** or sibling **`../stitch-app`** only. See **[docs/stitch/MIGRATION.md](docs/stitch/MIGRATION.md)**.

### Stitch single-window GUI

The canonical bundled flow now starts from **stitch-app** (`Stitch.bat` there). It uses this repo for backend bridge capabilities when needed.

1. **Build** the Stitch desktop bundle inside **stitch-app** (`npm run build`).
2. **Run** `Stitch.bat` from **stitch-app** root.
3. Keep this repo available for the bridge runtime (`stitch_rag_bridge.py`) on `127.0.0.1:8765`.

### Stitch as a native desktop app (no long manual command chain)

Stitch’s `apps/desktop` package uses **Tauri** for a real windowed app (`npm run dev` there = `tauri dev`). Run these from **stitch-app**:

| Goal | What to do |
|------|----------------|
| **One double-click** (bridge + sync + Tauri) | Run **`Stitch-Desktop.bat`** in the **stitch-app** repo root. |
| Same from a terminal | `npm run dev` (stitch-app) |
| Bridge already running | Start Stitch from stitch-app and keep this repo bridge on `127.0.0.1:8765` |
| Tauri only (you start the bridge yourself) | `npm run dev:desktop` |
| Browser tab only (no Tauri) | `npm run dev:browser` (uses **stitch-app** via `run-stitch-ui.mjs`) |
| Packaged `.exe` / installer | `npm run build:stitch-app` (after [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) and a **stitch-app** clone) |

You need **Node** on `PATH` and a local **stitch-app** clone (sibling **`../stitch-app`** or **`STITCH_APP_ROOT`**). The first Tauri dev run may compile Rust dependencies (one-time wait).

### Quick regression run

To run the v1 prompt suite against your local PDF corpus:

```bash
python rag_regression.py
```

This prints each response payload and a small summary (sourced count, fallback count, low-confidence count).

### Stitch JSON contract tests

```bash
python -m unittest tests.test_rag_stitch_contract -v
```

Validates `rag_stitch_contract._to_stitch_view` shapes (`answered` vs `fallback`, `show_sources`, optional `debug_retrieval_cards`).

If MCP-security prompts fall back, add the MCP landscape paper to `data/`:
- [Model Context Protocol (MCP): Landscape, Security Threats, and Future Research Directions](https://arxiv.org/pdf/2503.23278.pdf)

## πŸ› οΈ Project Structure

```
linkup_mcp/
β”œβ”€β”€ server.py             # MCP entrypoint (stdio) β€” all MCP tools live here
β”œβ”€β”€ agents.py             # spawn_agent sub-agent system (Ollama)
β”œβ”€β”€ rag.py                # RAGWorkflow (LlamaIndex + Ollama)
β”œβ”€β”€ rag_heuristics.py     # Pure scoring heuristics (unit-tested, no LLM imports)
β”œβ”€β”€ rag_runtime.py        # Lazy shared RAG index (MCP + bridge)
β”œβ”€β”€ rag_stitch_contract.py  # Stitch view JSON contract + guide-grounded help
β”œβ”€β”€ local_whisper_stt.py  # faster-whisper loader/transcribe (MCP + bridge share it)
β”œβ”€β”€ stitch_rag_bridge.py  # HTTP bridge ENTRYPOINT β€” owns Flask app, registers bridge/
β”œβ”€β”€ bridge/               # Bridge route modules (one per concern)
β”‚   β”œβ”€β”€ rag_routes.py     #   /api/rag/stitch, /api/rag/stitch-help, /api/stitch-user-guide
β”‚   β”œβ”€β”€ voice_routes.py   #   /api/voice/transcribe + STT engine selection
β”‚   β”œβ”€β”€ face_routes.py    #   /api/face/* (DeepFace imports deferred)
β”‚   β”œβ”€β”€ health.py         #   /health + /api/health (shared payload)
β”‚   β”œβ”€β”€ spa.py            #   /, /favicon.ico, optional built-SPA serving
β”‚   β”œβ”€β”€ cors.py           #   STITCH_ALLOWED_ORIGINS handling
β”‚   └── errors.py         #   JSON error handler for /api/face|auth|subscriptions
β”œβ”€β”€ stitch_auth/          # Google OAuth (PKCE), sessions, subscriptions SQLite
β”œβ”€β”€ face_verification/    # Local 1:1 face match + liveness (used by bridge)
β”œβ”€β”€ integrations/stitch/  # Pointer README β€” UI lives in stitch-app repo
β”œβ”€β”€ docs/                 # e.g. stitch_user_guide.md (bridge Help / Ask Stitch)
β”œβ”€β”€ data/                 # Your documents
β”œβ”€β”€ tests/                # Contract + unit tests (see CI commands below)
β”œβ”€β”€ pyproject.toml        # Dependencies + extras
β”œβ”€β”€ .cursorrules          # AI context for Cursor
└── .env                  # Environment variables (create this)
```

### Where to add things

| Change | Where |
|--------|-------|
| New MCP tool | `server.py` (`@mcp.tool()`); shared logic in a root module |
| New bridge HTTP route | New/existing module in `bridge/` (Flask blueprint), register in `stitch_rag_bridge.py` |
| Auth / subscriptions route | `stitch_auth/flask_routes.py` |
| RAG scoring/threshold tweak | `rag_heuristics.py` (pure, unit-tested) or per-instance attrs on `RAGWorkflow` |
| Bridge JSON shape change | `rag_stitch_contract.py` **and** `packages/stitch-api-types` (keep in sync, bump that package) |

`stitch_rag_bridge.py` must keep exporting `app` and `register_stitch_spa_routes` β€” stitch-app's `stitch_gui.py` imports them by name.

### Test matrix

```bash
# Default profile (uv sync):
python -m unittest tests.test_rag_stitch_contract tests.test_rag_heuristics -v

# With stitch-bridge extra:
python -m unittest tests.test_face_storage tests.test_bridge_routes tests.test_stitch_auth_helpers -v
```

## πŸ”§ How It Works

```
Cursor IDE β†’ MCP Server (server.py)
                 ↓
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚  RAG Tool   β”‚  Web Searchβ”‚
    β”‚  (rag.py)   β”‚  (Linkup)  β”‚
    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           ↓
    Ollama (llama3.2) - runs locally
```

## πŸ’° Cost

| Tool | Cost |
|------|------|
| RAG | **$0** (local Ollama) |
| Web Search | ~$10-50/month (Linkup API) |
| Ollama | **$0** (runs locally) |

## πŸ› Troubleshooting

**MCP server not loading?**
1. Check Ollama is running: `ollama list`
2. Verify path in `mcp.json`
3. Check Cursor logs: `%APPDATA%\Cursor\logs\`

**Ollama connection refused?**
```bash
ollama serve
```

## πŸ” Privacy

- βœ… **RAG Tool**: 100% local, documents never leave your machine
- βœ… **Ollama**: Runs locally, no cloud API calls
- ⚠️ **Web Search**: Queries sent to Linkup servers

## πŸ“– Related Projects

| Repository | Purpose |
|------------|---------|
| [chatbot-rag-core](https://github.com/RanneG/chatbot-rag-core) | Reusable Python RAG library |
| [chatbot-api-server](https://github.com/RanneG/chatbot-api-server) | Production Docker API server |

## πŸŽ“ Resources

- [MCP Documentation](https://modelcontextprotocol.io/)
- [Linkup API](https://www.linkup.so/)
- [LlamaIndex](https://docs.llamaindex.ai/)
- [Ollama](https://ollama.ai/)

## πŸ“ License

MIT License - See [LICENSE](LICENSE)

## πŸ™ Credits

- Original: [patchy631/ai-engineering-hub](https://github.com/patchy631/ai-engineering-hub)
- [Linkup](https://www.linkup.so/) for web search
- [LlamaIndex](https://www.llamaindex.ai/) for RAG
- [Ollama](https://ollama.ai/) for local AI

---

**Made with ❀️ for Cursor IDE users**