io.github.apollion69/mcp-portal
Cursor CLI delegation for verified bulk reads and reference-driven code generation.
Open source Open in the app JSON README (API)
About
Cursor CLI delegation for verified bulk reads and reference-driven code generation.
Details
- Kind
- MCP servers
- Topic
- Developer tools
- Publisher
- apollion69
- Origin
- official
- Category
- ferramentas
- Transport
- local
- Version
- 0.1.0
- Last push
- 2026-09-11T11:33:36Z
- Repository state
- ativo
- Language
- Python
- License
- MIT
- Added
- 2026-09-11 12:04:08
- Updated
- 2026-09-11 12:04:08
- Origin id
io.github.apollion69/mcp-portal
README
# mcp-portal
<!-- mcp-name: io.github.apollion69/mcp-portal -->
[](https://github.com/apollion69/mcp-portal/actions/workflows/ci.yml) [](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).