Skip to main content
Glama

pi-modal-mcp

Drive Modal serverless compute (GPU workers + headless Chromium) from any MCP-aware agent. Zero local dependencies for the remote path; a tiny stdio shim for stdio-only clients like pi.

Three pieces, one system:

agent (pi / Claude Desktop / Cursor / your Linux box)
  │
  │  MCP (stdio shim OR streamable HTTP)
  ▼
Modal frontend  ── FastAPI + MCP-over-HTTP
  │  (.remote.aio fan-out)
  ├─▶ run_model containers   (CPU workers, parallel)
  └─▶ browse_page container  (Playwright + Chromium → PNG)

What's inside

Path

What

modal-frontend/app.py

Modal app: FastAPI REST endpoints and a Modal-hosted MCP-over-HTTP server (stateless streamable HTTP). Deploys Playwright for screenshots.

modal-mcp/modal_mcp.py

Stdio MCP shim. Bridges stdio-only MCP clients (e.g. pi) to the Modal REST endpoints. Returns screenshots as MCP image content.

mcp-runtime/index.ts

Pi extension: a native MCP runtime for pi. Reads ~/.pi/agent/mcp.json, spawns stdio MCP servers, registers their tools as pi tools.

mcp-runtime/mcp.json.example

Example pi MCP config.

Related MCP server: Modal MCP Server

Tools exposed

Tool

Does

modal_ping

Health-check the Modal frontend.

modal_models

Fan out N parallel CPU workers on Modal. Returns JSON: concurrent count, elapsed seconds, per-worker results.

modal_browse

Open a URL in headless Chromium inside a Modal container. Returns a PNG screenshot (MCP image content).

modal_swarm

Run a prompt across N parallel OSS model workers on Modal GPUs (a model swarm). Returns JSON: {concurrent, elapsed, results:[{model, completion, elapsed}]}. Models are HuggingFace IDs (default Salesforce/codegen-350M-mono), cached in a shared Modal Volume.

Deploy the Modal app

Requires the Modal CLI authenticated.

cd modal-frontend
modal deploy app.py

You'll get two URLs:

web:      https://YOUR-WORKSPACE--pi-frontend-web.modal.run       (REST: /api/*)
mcp_web:  https://YOUR-WORKSPACE--pi-frontend-mcp-web.modal.run/mcp  (MCP streamable HTTP)

Why stateless?

Modal load-balances each HTTP request across containers, so stateful MCP sessions (which require session affinity) break. The app sets stateless_http=True and disables DNS-rebinding host checks so any host can reach it. For a private deployment, tighten transport_security.allowed_hosts.

Use it

Remote MCP client (Claude Desktop / Cursor / your Linux box)

{
  "mcpServers": {
    "modal": {
      "url": "https://YOUR-WORKSPACE--pi-frontend-mcp-web.modal.run/mcp"
    }
  }
}

Or the mcp Python client:

pip install mcp
python3 - <<'PY'
import asyncio
from mcp.client.streamable_http import streamablehttp_client
from mcp.client.session import ClientSession
async def main():
    async with streamablehttp_client("https://YOUR-WORKSPACE--pi-frontend-mcp-web.modal.run/mcp") as (r,w,_):
        async with ClientSession(r,w) as s:
            await s.initialize()
            print([t.name for t in (await s.list_tools()).tools])
            res = await s.call_tool("modal_browse", {"url": "example.com"})
            print([getattr(c,'type',None) for c in res.content])
asyncio.run(main())
PY

pi (native remote transport, no shim)

The mcp-runtime extension now speaks the remote (streamable-HTTP) MCP transport directly, so pi can hit the Modal-hosted MCP server with zero local component.

  1. Install the extension:

mkdir -p ~/.pi/agent/extensions/mcp-runtime
cp mcp-runtime/index.ts mcp-runtime/package.json ~/.pi/agent/extensions/mcp-runtime/
cd ~/.pi/agent/extensions/mcp-runtime && npm install
  1. Add ~/.pi/agent/mcp.json (replace workspace + URL):

{
  "mcpServers": {
    "modal": {
      "url": "https://YOUR-WORKSPACE--pi-frontend-mcp-web.modal.run/mcp",
      "transport": "streamable-http"
    }
  }
}
  1. Restart pi (or /reload), then /mcp list to see mcp_modal_modal_ping, mcp_modal_modal_models, mcp_modal_modal_browse tagged [remote].

pi (stdio fallback) via the shim

For MCP servers that only expose a stdio interface (or for air-gapped use), the modal-mcp shim still works:

  1. Install the shim on PATH:

cp modal-mcp/run.sh /usr/local/bin/modal-mcp   # or anywhere on PATH
chmod +x /usr/local/bin/modal-mcp
  1. Add ~/.pi/agent/mcp.json using the command form instead of url:

{
  "mcpServers": {
    "modal": {
      "command": "modal-mcp",
      "args": [],
      "env": {
        "MODAL_FRONTEND": "https://YOUR-WORKSPACE--pi-frontend-web.modal.run"
      }
    }
  }
}
  1. /mcp list shows the tools tagged [stdio].

Why stateless + 405 GET?

Modal load-balances each HTTP request across containers, so stateful MCP sessions (which require session affinity) break. The app sets stateless_http=True and disables DNS-rebinding host checks so any host can reach it.

A second subtlety: the MCP Node SDK's streamable-HTTP client, on start(), first tries GET /mcp to open a listening SSE stream; if the server returns 200 and holds it open, the client blocks waiting for events that a stateless server never delivers. A stateless server has no stream to offer, so the app wraps the MCP ASGI app in a middleware that returns 405 for GET /mcp, telling strict clients to skip the GET stream and use POST-inline responses. This makes the endpoint compatible with the Node SDK, the Python SDK, Claude Desktop, and Cursor alike.

For a private deployment, tighten transport_security.allowed_hosts.

Transports

The pi mcp-runtime extension supports both backends from one config:

  • stdio{ "command": "...", "args": [...] } spawns a local process.

  • remote{ "url": "https://...", "transport": "streamable-http" } talks to a remote MCP-over-HTTP server directly. "transport": "sse" selects the legacy SSE client. Optional "headers": { ... } for auth.

Remote transport means pi needs zero local component for cloud-hosted MCP servers — no shim, no npx/Python process to spawn.

AI-native IDE (model swarm)

The app serves a web IDE at /ide on the web URL: a Monaco editor whose "Run swarm" button fans the current buffer across N parallel OSS model workers on Modal GPUs and shows each completion in a side panel. This is the self-hosted, OSS-brain equivalent of a cloud coding agent — local UI, remote GPUs, no API keys.

https://YOUR-WORKSPACE--pi-frontend-web.modal.run/ide

Models default to Salesforce/codegen-350M-mono (small, fast, public) and are cached in a shared Modal Volume so cold starts after the first only pay the model-load cost. Pass any HuggingFace ID in the models field; gated models need a secrets=[modal.Secret.from_name("huggingface-secret")] on llm_worker.

How many models can I run simultaneously?

Modal scales each function horizontally — there's no fixed concurrency cap. You can fan out hundreds of concurrent .remote() calls; Modal spins up containers on demand. What actually bounds you:

  • Concurrent containers quota per workspace (free tiers are low; extra calls queue, they don't fail).

  • GPU availability for gpu=... functions.

  • Cold starts — first container of a function takes a few seconds; warm containers are ~instant.

  • Your fan-out shapeasyncio.gather(*[fn.remote.aio(x) for x in items]) issues all calls concurrently.

License

MIT

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
0dRelease cycle
9Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/salus-ryan/pi-modal-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server