ModelRunner
<a href="https://modelrunner.ai">ModelRunner</a> is a hosted remote MCP (Model Context Protocol) server that lets AI assistants like Claude and Cursor run…
What it can do
- List Models: Lists publicly available models. Filter by category or input/output modality. Returns compact summaries — use `get_model` for full details.
- Recommended Models: Returns the admin-curated shortlist of models for a category. The fastest way for an AI assistant to pick a good model.
- Get Model: Returns a compact, readable description of a model: inputs, outputs, pricing, examples. For raw JSON Schema use `get_model_raw_schema`.
What data it sees
Do you need an account
No: the server works without sign-in
ModelRunner is a hosted remote MCP (Model Context Protocol) server that lets AI assistants like Claude and Cursor run 100+ AI models — text-to-image, image-to-image, text-to-video, image-to-video, video-to-video, music generation, speech-to-text, and image-to-3D, including Kling, HiDream, Hunyuan Image, Stable Audio, and Rodin. One connection exposes every model as a callable tool: search the catalog, inspect a model's input schema, run inference with run_model, and get results back as hosted URLs directly in the conversation.
Unlike single-vendor MCP servers, ModelRunner aggregates models from multiple providers behind one endpoint and one billing account — pay-per-use credits, no subscription. Authorization is OAuth 2.1: your assistant opens a browser window once; no API key is ever pasted into a config file. Beyond inference, the server can upload local files for use as model inputs and author reusable "wrappers" — simplified, purpose-built interfaces over base models.
Endpoint: https://mcp.modelrunner.run/mcp (Streamable HTTP; registry name ai.modelrunner/mcp) Setup and full tool reference: ModelRunner MCP setup guide · GitHub
How to connect
How to connect ModelRunner to Claude
The server address to paste into Claude settings:
https://modelrunner-mcp--modelrunner.run.tools
- Open Claude (claude.ai or the desktop app).
- Go to Settings → Connectors.
- Click Add custom connector.
- Paste the server address copied below into Remote MCP server URL and click Add.
- In a chat, click + → Connectors and switch the new connector on.
Custom connectors are available on Free, Pro, Max, Team and Enterprise plans (Free is limited to one). On Team and Enterprise an organization Owner adds the connector first under Organization settings → Connectors.
Authorization
After you click Add, a sign-in window for the service opens. Sign in with your own account and approve access. Claude never sees your password.
How to connect ModelRunner to ChatGPT
The server address to paste into ChatGPT settings:
https://modelrunner-mcp--modelrunner.run.tools
- Open ChatGPT in a browser (chatgpt.com). A Plus, Pro, Business, Enterprise or Edu plan is required.
- Turn on developer mode once: Settings → Apps → Advanced settings → Developer mode.
- Open Settings → Connectors and click Create.
- Fill in the form: Name (anything), Description (one line about what the service does), MCP server URL (copy it below).
- Under Authentication choose OAuth if the service requires sign-in, otherwise None. Click Create.
- In a new chat open + → Apps/Connectors and enable the connector.
OpenAI has renamed this section before (Connectors → Apps/Plugins). If the label differs, search settings for "developer mode". On Business/Enterprise workspaces an admin must allow custom connectors first.
Authorization
On first use ChatGPT opens the service's sign-in window. Sign in and approve access.
How to connect ModelRunner to Cursor
The server address to paste into Cursor settings:
https://modelrunner-mcp--modelrunner.run.tools
Fastest: click Open in Cursor below and confirm the prompt.
Manually:
- In Cursor open Settings → Cursor Settings → MCP and click Add new global MCP server.
- Paste the JSON copied below into
~/.cursor/mcp.json(per project:.cursor/mcp.jsonin the repo root). - Save the file. The server appears in the MCP list; authorize it there if asked.
Authorization
If the service needs sign-in, an authorize button appears next to the server in the MCP list.
Server tool list (23)
Raw names from tools/list. Only developers need these.
| list_models | Lists publicly available models. Filter by category or input/output modality. Returns compact summaries — use `get_model` for full details. |
| recommended_models | Returns the admin-curated shortlist of models for a category. The fastest way for an AI assistant to pick a good model. |
| get_model | Returns a compact, readable description of a model: inputs, outputs, pricing, examples. For raw JSON Schema use `get_model_raw_schema`. |
| get_model_raw_schema | Returns the raw OpenAPI-style JSON Schema for a model. Prefer `get_model` for most use cases. |
| list_wrappers | Lists publicly available wrappers (curated combinations of base models with prompt templates). Returns compact summaries — use `get_wrapper` for full details. |
| list_my_wrappers | Lists the wrappers YOU own — scoped to your authenticated identity, including private and draft wrappers (which `list_wrappers` does not show). Returns each wrapper's `id`, `endpoint`, `visibility`, and `status`; pass that `id` to `patch_wrapper` or `delete_wrapper`. Requires authentication. |
| recommended_wrappers | Returns the admin-curated shortlist of wrappers for a category. |
| get_wrapper | Returns a compact, readable description of a wrapper: inputs, outputs, base models, and effective pricing (with markup). This readable view truncates long enum lists ("…N more") and omits the prompt template and field mappings. To edit a wrapper, fetch ONLY the part(s) you need with the focused tools — `get_wrapper_raw_schema` (full input schema + untruncated enums), `get_wrapper_prompt_template` (the Handlebars template + templateContext), `get_wrapper_field_mappings` (per-base-model mappings + defaultBaseModel). These are deliberately split so you pull just one slice into context; for a large wrapper, fan each part out to its own sub-agent rather than loading the whole definition at once. |
| get_wrapper_raw_schema | Returns ONLY the raw OpenAPI-style input `schema` for a wrapper, with the FULL, untruncated enum value lists that `get_wrapper` abbreviates ("…N more"). This is the authoritative source for allowed field values — do not reconstruct enums from a client app. Pair with `get_wrapper_prompt_template` and `get_wrapper_field_mappings` (separate calls) only when you actually need those parts. |
| get_wrapper_prompt_template | Returns ONLY the wrapper's Handlebars `promptTemplate` and its `templateContext` ({ maps, constants }) — nothing else. Use this when you intend to read or edit the template. Note it can be large (up to ~50k chars), which is exactly why it is a separate tool: fetch it in its own context/sub-agent rather than alongside the schema and mappings. |
| get_wrapper_field_mappings | Returns ONLY the wrapper's base-model wiring: the `defaultBaseModel` endpoint and each base model's `fieldMappings` (collectFields/staticValues/presets/renames/omit). Shaped to match `patch_wrapper`/`create_wrapper` input (`baseModels: [{ baseModel, fieldMappings }]`, defaultBaseModel as an "ownerName/alias" endpoint) so you can edit and submit it back. Fetch separately from the schema/template so each part stays in its own context. |
| wrapper_authoring_guide | Returns the full ModelRunner wrapper authoring guide: how to design the user-facing input schema, write the Handlebars prompt template + templateContext, choose base models, and derive per-base-model fieldMappings — with all constraints and worked examples. Read this BEFORE calling create_wrapper or patch_wrapper. |
| preview_wrapper | Dry-run a wrapper prompt template + field mappings against a sample input WITHOUT creating anything. Returns the transformed base-model input so you can validate template rendering, collectFields, presets, renames, and omit while authoring. Note: preview does not guarantee every produced key is accepted by the base model — create/update validation is stricter. |
| create_wrapper | Create a new wrapper you own (a curated base model + prompt template + user-facing input schema). It is created under YOUR account — `ownerName` is derived from your authenticated identity; do not pass it. Defaults to visibility=private, status=draft. BEFORE calling: (1) read `wrapper_authoring_guide`; (2) inspect each base model's app Input schema via `get_model` / `get_model_raw_schema` so your fieldMappings target valid keys; (3) dry-run with `preview_wrapper`. Constraints: `schema` is OpenAPI-style `{ components: { schemas: { Input: {...} } } }` and MUST NOT contain a `base_model` field. `promptTemplate` is Handlebars (helpers: lookup, eq, and, or, exists; no partials or raw blocks). Base models are referenced by their `ownerName/alias` endpoint (the same identifier you call a model with), not a UUID; `defaultBaseModel` must be one of `baseModels[].baseModel`. fieldMappings target each base model's APP schema and apply in order: collectFields → staticValues → presets → renames → omit. `alias` must be unique across your models and wrappers. Author a short "How to use" / orchestration passage INTO `description` — `get_wrapper` shows it to callers before they run. Especially when the wrapper is meant to produce a consistent SET across multiple runs, tell the caller to plan the set up front, generate the first item, then reuse that first output as the reference/anchor for the rest (not chain each run to its predecessor). |
| patch_wrapper | Update one of your existing wrappers by id. Provide only the fields you want to change; you can only modify wrappers you own. `ownerName` cannot be changed. Before editing, read the wrapper's current source with the focused tools — `get_wrapper_raw_schema`, `get_wrapper_prompt_template`, `get_wrapper_field_mappings` — fetching only the part you intend to change (`get_wrapper` truncates enums and omits the template/mappings). For a large or multi-part edit, fan each part out to its own sub-agent so no single context holds the whole definition. The same authoring constraints as `create_wrapper` apply to any changed field (schema shape, Handlebars helpers, fieldMappings target the base model app schema, base models referenced by `ownerName/alias` endpoint, defaultBaseModel ∈ baseModels). Re-run `preview_wrapper` after changing the template or mappings. |
| delete_wrapper | Delete one of your wrappers by id or "ownerName/alias" endpoint. You can only delete wrappers you own. |
| search | Relevance-ranked free-text search across the public catalog (models AND wrappers), designed for you to evaluate the results: - `score`: full-text rank, comparable across models and wrappers. - `matchedOn`: which fields matched (name/alias/tags/shortDescription/description/category/owner); 'fulltext' = only the stemmed query matched, no verbatim substring. - `pricing.averageCost`: average USD per run from curated examples. - `examplePrompts`: real prompts from example runs — proof of what the entry actually handles. - `collections`: published themed sets it belongs to. - `rootModel`: entries sharing it are variants (speed/price tiers) of one family — compare their pricing before picking. Recall-first (default 25 results) — YOU are the precision layer; filter and re-rank against the user's use case. Describe the use case in `q` with descriptive words ("remove background product photo"), not stopword sentences. Follow up with `get_model` / `get_wrapper` to see input schemas before running. |
| run_model | Starts an asynchronous inference request against the specified model or wrapper endpoint. Returns immediately with a requestId — poll with `get_request` or use `wait_for_request`. |
| get_request | Returns the current status, outputs (if completed), pricing snapshot, and error (if failed) for a request. |
| wait_for_request | Polls `get_request` server-side until the request reaches a terminal state or the timeout elapses. Returns the final status payload. |
| create_upload_url | PREFERRED way to turn a LOCAL file into a ModelRunner file-input URL. Returns two short-lived PUT URLs plus the final canonical `file_url`, so you stream the bytes straight to storage from your own environment — the bytes never pass through this MCP or your token output. Use this for any local file (especially images/audio/video over ~100 KB): (1) call this tool, (2) PUT the file with the returned headers using your shell, starting with `upload_url` (direct to storage) — e.g. `curl -X PUT -T <localPath> -H "Content-Type: <type>" "<upload_url>"`; if that PUT is blocked by a host/egress allowlist (HTTP 403 `host_not_allowed` / "not allowed host"), retry the SAME PUT to `relay_upload_url` (served on the ModelRunner API domain, which an allowlist is more likely to permit), (3) pass `file_url` as the model/wrapper input. ALWAYS prefer this over `upload_file` for local files — `upload_file` sends the bytes through the model as base64, which burns a large amount of tokens/compute and is unreliable above a few hundred KB. URLs are short-lived (PUT promptly). Requires a shell or other way to make an HTTP PUT; only if you have none, AND BOTH URLs are blocked by your network, fall back to `upload_file` (base64 — token-expensive, last resort). |
| upload_file | Uploads a file to ModelRunner storage and returns the canonical `file_url` for a `run_model` / wrapper file input. ⚠️ LAST RESORT for local files — almost always use `create_upload_url` instead. To upload LOCAL-file bytes this tool takes `base64`, which means the file is written into the MODEL'S TOKENS: that is slow and burns a large amount of tokens/compute, and it gets worse the bigger the file (and far worse in chunked mode — every chunk's base64 is emitted into the conversation). `create_upload_url` streams the bytes out-of-band via a shell PUT (direct-to-storage OR its relay URL on the ModelRunner domain) so NO base64 ever touches the model. Only fall back to base64 here when `create_upload_url` is genuinely impossible — i.e. you have no shell to PUT with AND it is a fully egress-locked sandbox where the MCP channel is the only way out. Re-hosting a remote `url` (no base64) is always fine. BASE64 MODES (last resort only). (1) SINGLE-SHOT: pass the whole file as `base64`; ALWAYS pass `expectedDecodedBytes` = the file's size in bytes so a silently-truncated base64 becomes a loud error instead of a corrupt upload. (2) CHUNKED (only when the file is too large to fit one base64 string in that locked sandbox — expensive, avoid otherwise): split into small base64 chunks (~32-48 KB each), one tool call per chunk; omit `uploadId` on the FIRST chunk (the response returns one), pass it back on every later chunk with an incrementing `partIndex`, set `isLastPart:true` on the final chunk. Each chunk carries its own `expectedDecodedBytes` and is validated on arrival — a truncated chunk errors alone and you resend just it (same `partIndex`). The response contains `file_url` once the last chunk assembles. Then pass `file_url` as the model/wrapper input. |
| list_my_requests | Returns the authenticated user's past requests, newest first. Filters by status, model endpoint, and date range. |