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**
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues