> ## Documentation Index
> Fetch the complete documentation index at: https://docs.instapods.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tools Reference

> All MCP tools available for AI assistants.

The InstaPods MCP server exposes 21 tools that AI assistants can call. These cover pod management, 1-Click Apps, file operations, environment variables, command execution, logs, and visitor feedback.

Every tool declares an `outputSchema`, so an assistant knows the shape of a result before calling it. Results come back as `structuredContent`, with a JSON text fallback for clients that ignore structured output.

## Pod Management

### list\_pods

List pods belonging to the authenticated team, optionally narrowed. With no parameters it returns every pod; to look up a single pod whose exact name you already know, use `get_pod` instead.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | No | Only return pods whose name or domain contains this text (case-insensitive) |
| `status` | string | No | `creating`, `running`, `stopped`, `error`, or `deleting` |
| `preset` | string | No | `static`, `php`, `nodejs`, `python`, or `go` |

**Returns:** `{ pods, count, total }` — `count` is the number of matches, `total` the number of pods the team owns before filtering. Each pod carries name, status, preset, plan, region, domain, URL, and resource allocation.

**Example prompt:** "Show me all my pods" · "Which of my pods are stopped?"

***

### get\_pod

Get details of a specific pod by name.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name |

**Returns:** `{ pod }` with status, domain, URL, preset, plan, region, resource allocation, and a `readiness` object (below). Internal fields (container IP, SSH port, server/team identifiers) are not exposed over MCP.

**Example prompt:** "What's the status of my-api?" or "Is my-api actually up?"

#### Readiness

`status` says whether the *container* is running. `readiness.state` says whether a visitor to the URL would actually see your application — the two are not the same, and a container can be up while the app inside it is not answering.

| State | Meaning |
| - | - |
| `creating` | The container is still being provisioned |
| `installing` | A 1-Click app is still installing or updating |
| `starting` | Running, but no probe has reached it yet |
| `healthy` | The proxy upstream answered — visitors see the app |
| `unhealthy` | The upstream refused or timed out — visitors see a 502 |
| `stopped` / `suspended` / `migrating` / `error` / `deleted` | The pod is not serving, for that reason |
| `unknown` | Nothing probes this pod, so InstaPods cannot say |

`readiness.detail` explains any state other than `healthy`, and `readiness.port` is the port inside the pod that the public URL is proxied to — what your app must listen on.

<Warning>
  `readiness.stale: true` means nothing is currently refreshing this pod's health, so the state is a reading from the past rather than now. Do not report a stale verdict as the pod's current condition — open the URL instead.
</Warning>

<Note>
  1-Click app pods are not continuously probed, so a running one reports `unknown` rather than `healthy`. That is InstaPods declining to claim a check it does not run — it is not a sign the app is down.
</Note>

***

### create\_pod

Create a new pod on your InstaPods account, billed monthly to that account. The tool checks that your
billing is in order (payment method, subscription) and opens your monthly billing cycle on a first
pod — with one exception: if you have no payment method yet, a custom-code create through the
connector may be granted a [free trial pod](/mcp/trial) instead of being refused. That pod is held in
an InstaPods-owned trial account and is never billed. The response says so when it is.

**No payment is ever taken in the conversation.** Payment methods, invoices and receipts are managed
by you on [instapods.com](https://app.instapods.com/dashboard/billing); no InstaPods MCP tool asks
for card details or moves money.

There are two ways to call it, and they answer different requests:

* **`app_type`** — deploy a ready-to-run application from the [1-Click App catalog](https://instapods.com/apps). The app is pre-baked into the pod image, so it boots working at the pod's URL and nothing is uploaded. The image carries its own runtime, so leave `preset` empty.
* **`preset`** — create an empty pod on a runtime, then upload code to it with `write_file`. To ship code you already have, `deploy_pod` does the whole thing in one call instead.

Pass one or the other; passing neither is an error.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name (DNS-safe, lowercase, max 63 chars) |
| `app_type` | string | No | 1-Click App slug from `list_apps` (e.g. `n8n`, `uptime-kuma`, `vaultwarden`) |
| `preset` | string | No | `static`, `php`, `nodejs`, `python`, or `go`. Required unless `app_type` is set |
| `plan` | string | No | Plan slug. Default: `launch`, or the app's minimum plan when `app_type` is set. Options: `launch`, `build`, `grow`, `scale`, `turbo` |
| `region` | string | No | Region slug (auto-selected if omitted) |

Each 1-Click App declares a minimum plan (n8n needs Build, for example). Omitting `plan` deploys on that minimum; naming a plan below it is **rejected**, not silently upgraded — the error names the required plan and its monthly price so the assistant can check with you before anything is billed.

**Returns:** Created pod object. Its `plan_slug` is the plan the pod is actually billed on.

**Example prompt:** "Create a Python pod called data-api on the build plan" · "Deploy n8n for me"

***

### deploy\_pod

Deploy your own code to a live HTTPS URL in **one call**: creates the pod if it does not exist, uploads every file, installs dependencies, restarts the app, and checks the URL actually answers before reporting success. It is the tool to use when the assistant already has your code — `create_pod` + `write_file` + `manage_pod` is the same thing spread over four or more calls.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name (DNS-safe, lowercase, max 63 chars). An existing pod of this name is **updated**, not replaced |
| `files` | array | Yes | The files to deploy, each `{ path, content }`. Paths are relative to the pod's app root. Max 50 files, 2 MiB total |
| `plan` | string | No | Plan slug. Default: `launch`, or `build` for `nodejs` and `go`, whose build step needs the extra memory |
| `region` | string | No | Region slug (auto-selected if omitted). Ignored when the pod already exists |

**Idempotent on `name`.** Deploying twice to the same name updates that pod rather than creating a second one or failing, so an assistant can safely retry a call that timed out.

**There is no `preset` parameter.** The runtime is detected from the file names — `package.json` → nodejs, `requirements.txt` or a `.py` file → python, `composer.json` or a `.php` file → php, `go.mod` → go, `index.html` → static. If nothing matches, the call fails and names the options rather than guessing, since a wrong guess silently skips the install or build step. (A pod created with `create_pod` and an explicit preset can then be deployed to by name.)

**Returns:** `{ pod, url, public, reason, created, preset, plan, files_written, deps_installed, service_active, warning }`.

`public` is the part that matters: it says whether an ordinary visitor's request to `url` actually succeeded, checked from outside the container after the deploy. A pod can exist, be running, and still not be serving anything anyone can see — `public: false` with a `reason` is how you find that out instead of being told the site is live.

1-Click Apps are **not** deployed this way; they ship their own code, so use `create_pod` with `app_type`.

**Example prompt:** "Put this landing page live at demo.instapods.app" · "Deploy this Flask app for me" · "Push my change and tell me if it's live"

***

### manage\_pod

Start, stop, restart, or reload a pod. Every action here is reversible and leaves your files alone — deleting is a separate tool, [`delete_pod`](#delete_pod), so an assistant can restart your app without stopping to ask permission each time.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name |
| `action` | string | Yes | `start`, `stop`, `restart`, or `reload` |

**Returns:** Updated pod object, or reload status with health check.

The **reload** action is the most powerful — it:

1. Auto-starts the pod if stopped
2. Installs dependencies (`npm install`, `pip install`, `composer install`)
3. Detects entry points and frameworks (gunicorn, Express, Laravel)
4. Restarts application services
5. Runs a health check and returns service status

**Example prompts:** "Restart my-api" · "Reload my-app after I changed the code"

***

### delete\_pod

Permanently delete a pod. The container, its files, and its database are destroyed and cannot be recovered, the URL stops working, and billing for it stops.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name |
| `reason` | string | No | Why you are deleting it, if you said: `just_testing`, `project_finished`, `redeploying`, `too_expensive`, `technical_issues`, `missing_feature`, `switching_provider`, `other` |
| `note` | string | No | Your own words about why |

**Returns:** Deletion confirmation.

This is annotated as a destructive tool, so Claude asks you to confirm before it runs — every time, regardless of what you have approved before.

`reason` and `note` are the same optional exit survey the dashboard shows. Claude only passes them on if you have already said why — it will not hold up the deletion to ask, and a pod is never kept because the question went unanswered.

**Example prompts:** "Delete test-pod" · "Get rid of the staging pod, I'm done with it"

***

### change\_plan

Upgrade or downgrade a pod's plan. Adjusts CPU, memory, and disk to the target plan's limits.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name |
| `plan` | string | Yes | Target plan slug: `launch`, `build`, `grow`, `scale`, or `turbo` |

A downgrade is rejected if the pod's current disk usage would exceed the smaller plan's quota — free up space first. Use `list_plans` to see the limits and price of each plan.

This changes what your account is billed, prorated on its next monthly invoice. As with `create_pod`, no payment is taken in the conversation — cards and invoices stay on [instapods.com](https://app.instapods.com/dashboard/billing).

**Returns:** Updated pod object with the new CPU, memory, and disk allocation.

**Example prompts:** "Upgrade my-api to the grow plan" · "Move data-api down to build"

***

## File Operations

### list\_files

List files in a directory inside a pod.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name |
| `path` | string | No | Directory path (default: `~/app`). Must be within `/home/instapod`, `/var/www`, or `/tmp`. |

**Returns:** Array of file entries with name, permissions, size, and `is_dir` flag.

**Example prompt:** "Show me the files in my-api"

***

### read\_file

Read the contents of a text file inside a pod. Files over 5 MB and files that are not UTF-8 text are refused; read part of a large file with `exec_command` (`head -c`, `tail -c`) instead.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name |
| `path` | string | Yes | Absolute file path |

**Returns:** File content as text.

**Example prompt:** "Show me the package.json in my-api" or "Read /home/instapod/app/index.js"

<Note>
  A pod serves up to 4 file reads at a time, counted together with reads from the dashboard, Web IDE, CLI and API. A read beyond that waits up to 5 seconds for one to finish; if none does, the tool returns an error saying the pod is busy, and the call can be made again.
</Note>

***

### write\_file

Write content to a file inside a pod. Creates the file and parent directories if they don't exist.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name |
| `path` | string | Yes | Absolute file path |
| `content` | string | Yes | File content to write |

**Returns:** Confirmation with saved path.

**Example prompt:** "Create an index.js in my-api that serves a hello world Express app"

<Note>
  Files are written as the `instapod` user. Paths must be within `/home/instapod`, `/var/www`, or `/tmp`.
</Note>

***

## Environment Variables

Use these instead of hand-editing `.env` with `exec_command`. They write the file your app actually reads (a 1-Click App's own environment file where it has one), quote values so a multi-line key survives intact, keep the file readable only by the app, and restart the app so the values take effect.

<Warning>
  **Values are write-only.** No MCP tool ever returns the value of an environment variable — a tool response is rendered into your AI assistant's transcript, and secrets do not belong there. To read a value back, open the pod's Environment tab in the dashboard.
</Warning>

### list\_env\_names

List the names of a pod's environment variables and the file they live in.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name |

**Returns:** The variable names (sorted), the file path, and `warnings` - non-empty when the variables are **not** reaching the app right now (the file was edited over SSH, or the pod's service is not wired to read the injected copy). Any `set_env` repairs it. Never the values.

**Example prompt:** "Which environment variables are set on my-api?"

***

### set\_env

Set environment variables on a pod. Existing names are updated in place, new ones appended; names you do not pass are left alone.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name |
| `vars` | object | Yes | Object of `NAME` to value, e.g. `{"OPENAI_API_KEY": "sk-..."}` |

**Returns:** How many variables were set, the names affected, the file path, and any warnings about names that will not be applied - ones InstaPods manages itself, or ones a 1-Click App's own service fixes.

**Example prompt:** "Set OPENAI\_API\_KEY on my-api" (your assistant will ask you for the value)

<Note>
  Names must match `[A-Za-z_][A-Za-z0-9_]*`. A name InstaPods manages itself (such as `PORT`) can be overridden, but the response warns you — overriding one can take the pod offline.
</Note>

***

### delete\_env

Remove environment variables by name and restart the app.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name |
| `keys` | array | Yes | Names of the variables to remove |

**Returns:** How many were actually removed — a name that was not set is ignored.

**Example prompt:** "Remove STRIPE\_TEST\_KEY from my-api"

***

## Command Execution

### exec\_command

Run a shell command inside a pod. Runs as the `instapod` user (same as SSH access), from the pod's app root unless `cwd` says otherwise.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name |
| `command` | string | Yes | Shell command to execute |
| `cwd` | string | No | Directory to run in. Relative paths resolve against the app root; defaults to the app root itself |
| `timeout_seconds` | string | No | Seconds before the command is killed (default 60, maximum 600) |

**Returns:** `stdout` and `stderr` separately, the command's real `exit_code`, `duration_ms`, `timed_out`, `truncated`, and the `cwd` and `timeout_seconds` applied.

**Example prompt:** "Run `ls -la` in my-api" or "Install express in my-api with npm"

<Note>
  `exit_code` is the command's own status — a non-zero value means the command failed even though the tool call succeeded. A command killed at the timeout returns `exit_code: 124` with `timed_out: true`, and keeps whatever it had printed before the kill. Raise `timeout_seconds` for dependency installs and builds.

  `stdout` and `stderr` are each capped at 1 MB. A longer stream comes back as its first 256 KB and last 768 KB with a marker line between them saying how many bytes were left out, and `truncated` is `true`. For bulky output, send it to a file and read the part you need (`head -c`, `tail -c`, `grep`) instead of printing it.

  A pod runs up to 8 commands at a time, counted together with commands from the CLI and API. A command beyond that waits up to 5 seconds for one to finish; if none does, the tool returns an error saying the pod is busy, and the call can be made again. A long-running command holds its place until it exits or times out.
</Note>

<Warning>
  This tool executes arbitrary shell commands inside your pod. The AI assistant will typically ask for confirmation before running destructive commands.
</Warning>

***

## Logs

### get\_logs

Get application logs from a pod via journalctl — either the tail, or a search
across the whole journal.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name |
| `lines` | string | No | Number of log lines (default: `100`, max `10000`). With `grep`, this caps the matches returned, not the lines searched |
| `service` | string | No | Filter by service name (e.g., `app`, `nginx`) |
| `grep` | string | No | Only return entries matching this regex (PCRE) |

**Returns:** `{ pod, service, grep, logs }` — `logs` is the journalctl output,
and `grep` echoes the pattern back so it is clear the output is filtered.

With `grep`, matches print **newest first** (the opposite order to an unfiltered
read); an all-lowercase pattern matches case-insensitively while any uppercase
character makes it case-sensitive; and `-- No entries --` means the search ran
and nothing matched.

**Example prompt:** "Show me the logs for my-api", "Get the last 50 nginx logs from my-app", or "Search my-api's logs for ECONNREFUSED"

***

## Feedback

The feedback widget is a pinned-comment overlay InstaPods injects into a pod's served HTML. Turn it on, share the link with reviewers, then read and triage what they left — the same data the dashboard's Feedback tab shows.

### list\_feedback

List visitor feedback on a pod, plus whether collection is enabled and the link to share.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name |
| `status` | string | No | Only return feedback in this state: `new`, `read`, `resolved` |

**Returns:** `{ pod, enabled, visibility, share_url, feedback[], counts, count }`. Each comment carries `id`, `name`, `email`, `comment`, `page_path`, `element_text`, `status` and `created_at`. `counts` totals every status regardless of the filter.

**Example prompt:** "What feedback did people leave on my-site?" or "Show me the unresolved feedback on my-site"

***

### manage\_feedback

Turn the widget on or off, change who sees it, rotate the share token, or triage one comment.

| Name | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Pod name |
| `action` | string | Yes | `enable`, `disable`, `set_visibility`, `rotate_token`, `mark_new`, `mark_read`, `mark_resolved`, `delete` |
| `visibility` | string | No | `everyone` or `link`. Used by `enable` and `set_visibility`; defaults to `everyone` on first enable |
| `id` | string | No | Feedback id from `list_feedback`. Required by `mark_*` and `delete` |

`everyone` shows the widget to every visitor of the pod URL. `link` hides it unless the visitor opens the returned `share_url`, which carries the token — use it to collect feedback from a named group without exposing the widget publicly. `rotate_token` mints a new token, so links already handed out stop working.

Enabling re-renders the pod's nginx vhost to inject the widget, so the pod must be running. On a stopped pod the setting is stored and applies on the next start.

**Returns:** `{ action, status, pod, id?, enabled, visibility, share_url }`.

**Example prompt:** "Turn on feedback for my-site and give me a link to share" or "Mark that feedback resolved"

***

## Catalog

### list\_apps

List the 1-Click Apps that can be deployed ready-to-run — n8n, Uptime Kuma, Vaultwarden, Memos, Excalidraw and the rest of the catalog.

| Name | Type | Required | Description |
| - | - | - | - |
| `query` | string | No | Only return apps whose slug, name, description, category or replaced-SaaS list contains this text (case-insensitive) |

**Returns:** `{ apps, count }`. Each app carries `slug` (what to pass to `create_pod` as `app_type`), name, description, category, `replaces` (the commercial SaaS it stands in for), `min_plan` and `min_plan_price_monthly_cents`, and `required_env_vars` for apps that need configuring after deploy.

**Example prompt:** "What self-hosted apps can I deploy?" · "Is there a self-hosted alternative to Zapier?"

***

### list\_presets

List available pod presets.

**Parameters:** None

| Preset | Stack |
| - | - |
| `static` | Nginx |
| `php` | PHP 8.3 + Nginx |
| `nodejs` | Node.js 22 |
| `python` | Python 3.12 |

**Example prompt:** "What presets are available?"

***

### list\_plans

List available pricing plans.

**Parameters:** None

**Returns:** Array of plans with slug, name, CPU, memory, storage, and price.

**Example prompt:** "What plans do you have?"

***

### list\_regions

List available deployment regions.

**Parameters:** None

**Returns:** Array of regions with slug and server count.

**Example prompt:** "What regions can I deploy to?"


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.