Skip to main content
Glama
README.md
# mcp-portal

<!-- mcp-name: io.github.apollion69/mcp-portal -->

[![CI](https://github.com/apollion69/mcp-portal/actions/workflows/ci.yml/badge.svg)](https://github.com/apollion69/mcp-portal/actions/workflows/ci.yml) [![PyPI](https://img.shields.io/pypi/v/mcp-portal.svg)](https://pypi.org/project/mcp-portal/)

**mcp-portal** is a stdio [Model Context Protocol](https://modelcontextprotocol.io/) server that lets a frontier agent (Claude Code, Codex, Cursor, or any MCP host) delegate two jobs to the **Cursor CLI on its own quota**: bounded **`bulk_read`** (read explicitly selected files, answer with verified quotes) and **`code_write`** (generate boilerplate from a reference file + spec; the **server** writes the target file). Python stdlib only—no Node runtime and no MCP SDK dependency.

On 2026-09-08, `composer-2.5-fast` generated roughly **5× faster** than a frontier model on the same brief (line-rate measurement). Cursor quota is separate from the host model's.

## Quick start

```bash
uvx mcp-portal
```

Also available as `pipx install mcp-portal` / `pip install mcp-portal`, and listed in the
[MCP Registry](https://registry.modelcontextprotocol.io/) as `io.github.apollion69/mcp-portal`.
To run the development head instead of the release:

```bash
uvx --from git+https://github.com/apollion69/mcp-portal mcp-portal
```

Requirements: Python 3.10+, the [Cursor CLI](https://cursor.com/docs/cli) (`cursor-agent`) installed and logged in.

Doctor (CLI inventory, no model call):

```bash
mcp-portal-doctor
```

## Configure per host

### Claude Code

```bash
claude mcp add --scope user mcp-portal -- uvx mcp-portal
```

### Codex (`~/.codex/config.toml`)

```toml
[mcp_servers.mcp-portal]
command = "uvx"
args = ["mcp-portal"]
tool_timeout_sec = 150
```

### Cursor (`~/.cursor/mcp.json`)

```json
{
  "mcpServers": {
    "mcp-portal": {
      "command": "uvx",
      "args": ["mcp-portal"]
    }
  }
}
```

### VS Code (`.vscode/mcp.json`, `servers` key)

```json
{
  "servers": {
    "mcp-portal": {
      "type": "stdio",
      "command": "uvx",
      "args": ["mcp-portal"]
    }
  }
}
```

### Generic `mcpServers` JSON

```json
{
  "mcpServers": {
    "mcp-portal": {
      "command": "uvx",
      "args": ["mcp-portal"]
    }
  }
}
```

Environment (optional):

| Variable | Purpose |
|----------|---------|
| `MCP_PORTAL_HOME` | Cache, receipts, evidence (default `~/.cache/mcp-portal`) |
| `MCP_PORTAL_CLI` | Path to `cursor-agent` / `agent` |

## Tools

### `bulk_read`

| Argument | Required | Description |
|----------|----------|-------------|
| `paths` | yes | 1–16 file paths (relative to `root` or absolute) |
| `question` | yes | Question answered only from those files |
| `root` | no | Common root; default = longest common parent of `paths` |
| `model` | no | Override model; policy applies when omitted |

Returns `status`, `run_id`, `answer.findings[]` (`file`, `start`, `end`, `quote`, `fact`), `gaps[]`, `metrics`, `model_decision`.

### `code_write`

| Argument | Required | Description |
|----------|----------|-------------|
| `spec` | yes | What to generate |
| `reference_path` | yes | Style/context reference file |
| `target_path` | no | If set, server writes this path |
| `model` | no | Override model |

Returns generated `code`, optional `bytes_written`, `run_id`, `metrics`.

### `status`

No arguments. Returns CLI path, auth hint, default model, policy summary, cache location, receipt counters.

## Model policy

Shipped in `model-policy.json` (package data). Defaults:

- Prefer Cursor-native models (`composer-2.5`, then `cursor-grok-*`)
- Strip `-fast` suffixes (never auto-select fast variants)
- Other vendors only when **explicitly** requested and listed by `cursor-agent --list-models`

Override by editing `model-policy.json` in the installed package or setting policy fields via a custom file at `MCP_PORTAL_HOME` (future) — today, replace the package file or patch `preferred` in your fork. Each tool result includes `model_decision.reason` (`default_preferred`, `fast_suffix_stripped`, `cursor_native_explicit`, `explicit_other_vendor`, `requested_unavailable_fallback`).

## How it works

1. **Authorize** — Server reads only listed paths; blocks credential-like paths and secret patterns.
2. **Manifest** — Request JSON includes per-file SHA-256 hashes.
3. **Isolate** — Cursor CLI runs with fresh `CURSOR_CONFIG_DIR`, deny-all permissions, `--mode ask`, sandbox enabled.
4. **Verify** — Every `quote` in `bulk_read` answers must appear verbatim in the cited line range; bad citations are dropped or fail closed.
5. **Evidence** — Per-run directory under `MCP_PORTAL_HOME/runs/<run_id>/` with manifest (hashes, metrics; not full source).
6. **Budgets** — 16 files, 128 KiB combined input, 90s timeout, bounded stdio frames.

## Windows

On Windows, the delegate uses a **local** Cursor CLI run when either:

- `MCP_PORTAL_CLI` points at an executable (including test stubs), or
- `cursor-agent` / `agent` is found on `PATH` and is a real file.

Otherwise it falls back to the **`wsl.exe` bridge** into Ubuntu/WSL (`python3 -m mcp_portal.delegate --worker`). Force either mode with `MCP_PORTAL_BACKEND=local` or `MCP_PORTAL_BACKEND=wsl`.

- MCP config can use native `uvx mcp-portal` when the CLI is on PATH, or `wsl.exe` + `uvx mcp-portal` when it is not
- Helpers in `clients/windows/` (`delegate.ps1`, `parse_read.ps1`)
- `MCP_PORTAL_WORKER` overrides the default WSL worker command
- `MCP_PORTAL_WSL_CD` sets the WSL working directory (default `~`)

## Optional Claude Code routing hook

Install read gate + skill (generic, transactional):

```bash
python3 -m mcp_portal.install_router prepare --client claude --python python3 \
  --state-root ~/.cache/mcp-portal/router-tx --shell bash --command-shell bash
# then apply with the printed transaction id
```

See `docs/skills/cursor-bulk-reader/SKILL.md` for agent-facing guidance. The router blocks or warns on large full-file reads (>350 lines or >128 KiB) and points agents at `bulk_read`.

Repo-level MCP registration helper:

```bash
python3 -m mcp_portal.install plan
python3 -m mcp_portal.install apply --target claude-mcp
```

## Security

See [SECURITY.md](SECURITY.md). Summary: you choose which files leave the machine; the CLI runs read-only with tools denied; quotes are verified server-side. Not a substitute for secret hygiene.

## Related projects

Several **Node-based** bridges expose Cursor via MCP (different tradeoffs: SDK/Node stack, varying isolation and verification):

- [lipey1/cursor-agent-mcp](https://github.com/lipey1/cursor-agent-mcp)
- [andreilungeanu/cursor-delegate-mcp](https://github.com/andreilungeanu/cursor-delegate-mcp)
- [sailay1996/cursor-agent-mcp](https://github.com/sailay1996/cursor-agent-mcp)
- [ai-nuke/cursor-agent-mcp](https://github.com/ai-nuke/cursor-agent-mcp)
- [JaimeJunr/cursor-mcp-bridge](https://github.com/JaimeJunr/cursor-mcp-bridge)

**mcp-portal** focuses on stdlib Python, hash-pinned manifests, quote verification, server-side writes for `code_write`, model policy, and WSL-first Windows support.

## License

MIT — see [LICENSE](LICENSE).

TDQS

B3.1/5.0

Scored across 3 tools

Disambiguation5/5

Each tool serves a clearly distinct purpose: bulk_read is read-only Q&A, code_write generates and optionally writes code, and status reports configuration. There is no overlap in functionality that would cause misselection.

Naming Consistency3/5

bulk_read and code_write use snake_case but with different modifier patterns (bulk_read is modifier+verb, code_write is noun+verb), while status is a bare noun. The mix makes the convention only partially predictable.

Tool Count4/5

Three tools is within the typical 3-15 range and each earns its place for a focused portal. However, the surface feels slightly thin for a server handling both file reading and code generation.

Completeness3/5

The domain appears to be Cursor CLI operations for reading and writing code. Core create and read operations exist, but update, delete, and list operations are missing, leaving notable gaps for a full lifecycle.

Maintenance

ActivityMaintained
ResponsivenessNo issues