PRESTO-only MCP server. Typed, sandboxed, reproducible access to PRESTO radio-astronomy tools via Docker.
Project description
presto-mcp
A typed, sandboxed Model Context Protocol server that lets LLMs invoke real PRESTO (pulsar / radio-astronomy) tools — readfile, rfifind, prepfold — through Docker. Every invocation is sandboxed (--network none, read-only data mount, --pids-limit, no-new-privileges), persisted to a reproducible run manifest, and exposed as MCP resources.
PRESTO-only. Not PrestoDB, not Apache Pulsar, not PulsarX / TransientX / riptide / Heimdall / PULSAR_MINER.
What this MVP exposes
| MCP tool | What it does |
|---|---|
presto.readfile |
Parse SIGPROC filterbank / PSRFITS header metadata. |
presto.rfifind |
Find RFI; produce .mask/.rfi/.stats artifacts. |
presto.prepfold (Mode A) |
Fold at a known candidate period + DM. Produces .pfd / .pfd.bestprof. |
presto.list_runs |
List recent runs from the local runs/ directory. |
presto.get_run_manifest |
Return the full manifest for one run_id. |
| MCP resource | Contents |
|---|---|
presto://runs/{run_id}/manifest |
manifest.json for the run |
presto://runs/{run_id}/stdout |
captured PRESTO stdout |
presto://runs/{run_id}/stderr |
captured PRESTO stderr |
presto://runs/{run_id}/artifacts/{filename} |
one artifact (text inline; binary or >1 MiB returns a JSON descriptor) |
Prerequisites
- Python 3.11+ (3.11 is what
uv syncprovisions by default). - uv for environment management. The repo's
pyproject.tomlpins dev tools. - Docker Desktop / engine running on the host.
- PRESTO image pulled:
docker pull alex88ridolfi/presto5:png
Verify Docker + PRESTO
docker images alex88ridolfi/presto5:png
docker run --rm alex88ridolfi/presto5:png which readfile rfifind prepfold
# Expected: /software/presto5/installation/bin/{readfile,rfifind,prepfold}
Install
uv sync --extra dev
This creates .venv/, installs mcp==1.27.1, Pydantic v2, Typer, dev tools (pytest, ruff).
Copy .env.example to .env if you want to override defaults:
cp .env.example .env
Run the MCP server (STDIO)
uv run python -m presto_mcp.server
The server reads PRESTO_* env vars from .env and the environment, ensures runs/, outputs/, logs/ exist, runs a startup health check (docker --version, non-zero data file sizes), then speaks JSON-RPC on stdio.
From an MCP client
Copy examples/mcp/claude_desktop_config.example.json into your MCP client's config (e.g. Claude Desktop) and update the cwd to the absolute repo path:
{
"mcpServers": {
"presto": {
"command": "uv",
"args": ["run", "python", "-m", "presto_mcp.server"],
"cwd": "C:/ABSOLUTE/PATH/TO/MCP-Server-Presto"
}
}
}
MCP Inspector (recommended for interactive debugging)
npx @modelcontextprotocol/inspector uv run python -m presto_mcp.server
In the Inspector UI:
tools/listshould show all fivepresto.*tools.- Call
presto.readfilewith{ "input_file": "57762_12049_J0532+3305_000022.fil" }. - Call
presto.rfifindwith{ "input_file": "57762_12049_J0532+3305_000022.fil", "time": 2.0 }. - Read
presto://runs/<run_id>/manifestafter either call.
Tests
# Unit + integration (no Docker required)
uv run pytest -q
# Lint
uv run ruff check .
# End-to-end with real Docker + real data files
uv run pytest -q tests/e2e --run-e2e
E2E tests are skipped by default. They invoke the PRESTO image with real data, write real run dirs, and assert the parsed metadata matches expectations from data/57762_12049_J0532+3305_000022.fil.
Layout of one run
runs/
└── 20260516T143052Z-K7QM3A/
├── manifest.json
├── stdout.log
├── stderr.log
└── artifacts/
├── rfi_rfifind.mask
├── rfi_rfifind.rfi
├── rfi_rfifind.stats
├── rfi_rfifind.ps
├── rfi_rfifind.bytemask
└── rfi_rfifind.inf
run_id format: YYYYMMDDTHHMMSSZ-<6-char base32> (UTC + 30 bits entropy). Lexicographic order is chronological.
The manifest records the full Docker argv, the PRESTO argv, the image (and digest if Docker provides one), CPU/memory caps, timeout, status, exit code, durations, and the list of artifacts.
Security model (summary)
- All inputs go through
presto_mcp.path_security.resolve_input_path: rejects absolute paths,..segments, case-folding tricks, and paths outsideDATA_DIR. - All execution uses
subprocess.run(argv, shell=False, timeout=...). There is noshell=Trueand no genericrun_commandtool. - Every Docker invocation runs with
--network none,--security-opt no-new-privileges,--pids-limit 256,--cpus,--memory, a named container, and--rm.data/mounts read-only. - Resource URIs revalidate path-traversal at read time. Large/binary artifacts return a JSON descriptor rather than the file body.
- Errors are typed (
PathSecurityError,PolicyViolationError,DockerInvocationError,ParserError,ManifestError); never raw stack traces.
Full design notes in ARCHITECTURE.md. Repo conventions for AI coding agents in CLAUDE.md and AGENTS.md.
Known limits (MVP)
- Binary PRESTO outputs are not parsed.
.mask,.stats,.rfi,.pfdare recorded as artifacts and exposed as resources, but never opened/decoded server-side. prepfoldMode A only. Folding by accel-search candidate file (Mode B) is documented but not implemented — it requires upstreamaccelsearchplumbing which is out of scope.- STDIO transport only. Remote HTTP is deliberately deferred.
- No SQLite run index.
presto.list_runswalksruns/*/manifest.json. This is fine for thousands of runs; not for millions. - Image is tag-pinned, not digest-pinned at install time. The manifest captures the digest at runtime if Docker reports it.
Not in MVP
- PulsarX, TransientX, riptide, Heimdall, PULSAR_MINER, DRAFTS++.
- Workflow tools that chain
rfifind → prepsubband → accelsearch → prepfold. - A Python executor or generic shell tool. (And we won't be adding one.)
Project files
src/presto_mcp/server.py— FastMCP entrypoint (the only file that imports FastMCP).src/presto_mcp/docker_backend.py— argv builder +subprocess.run+ timeout/kill.src/presto_mcp/path_security.py— input-path traversal guards.src/presto_mcp/executor.py— orchestrates one run (paths → backend → parse → manifest).src/presto_mcp/parsers/*— PRESTO stdout parsers (MVP: stdout only).src/presto_mcp/tools/*— one module per PRESTO binary.src/presto_mcp/resources.py— MCP resource handlers.tests/fixtures/stdout/— captured real PRESTO stdout for contract tests.tests/fakes/fake_docker_backend.py— in-memory backend honoringBackendProtocol.
License
MIT (see pyproject.toml).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file presto_mcp-0.1.0.tar.gz.
File metadata
- Download URL: presto_mcp-0.1.0.tar.gz
- Upload date:
- Size: 122.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74c78579579e7122c97b833d6ee9f31e3cd021bacf7cd17ca3b0e66a551b6258
|
|
| MD5 |
f1b8ab84c72cc4b6aef4de0878e046a7
|
|
| BLAKE2b-256 |
205ad9d4c34f67beda4830ad4f0c3f5faf4c8fd2fbb8ee143ab9cbf784e12d4a
|
Provenance
The following attestation bundles were made for presto_mcp-0.1.0.tar.gz:
Publisher:
publish.yml on Kodamonkey/MCP-Server-PRESTO
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
presto_mcp-0.1.0.tar.gz -
Subject digest:
74c78579579e7122c97b833d6ee9f31e3cd021bacf7cd17ca3b0e66a551b6258 - Sigstore transparency entry: 1562835365
- Sigstore integration time:
-
Permalink:
Kodamonkey/MCP-Server-PRESTO@34d910f0bd75e4415adb2bf2fe97b78e953fd643 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Kodamonkey
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@34d910f0bd75e4415adb2bf2fe97b78e953fd643 -
Trigger Event:
push
-
Statement type:
File details
Details for the file presto_mcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: presto_mcp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 73.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c7fe01f0894e6a8dcc516aef3b6d156cea582cdf23562abedfc53752be22aa5
|
|
| MD5 |
768411e5e8af9dc75c2952daef572b10
|
|
| BLAKE2b-256 |
8f0fa74887f22a35efa55a9779ef12c44eb4a32af131fa7ce69e44350380c2e1
|
Provenance
The following attestation bundles were made for presto_mcp-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on Kodamonkey/MCP-Server-PRESTO
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
presto_mcp-0.1.0-py3-none-any.whl -
Subject digest:
5c7fe01f0894e6a8dcc516aef3b6d156cea582cdf23562abedfc53752be22aa5 - Sigstore transparency entry: 1562835379
- Sigstore integration time:
-
Permalink:
Kodamonkey/MCP-Server-PRESTO@34d910f0bd75e4415adb2bf2fe97b78e953fd643 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Kodamonkey
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@34d910f0bd75e4415adb2bf2fe97b78e953fd643 -
Trigger Event:
push
-
Statement type: