> Homerun serves an MCP server, so an AI agent such as Claude can diagnose and fix your services for you: read their logs, deploys and settings, change them, redeploy, roll back.
> Source: https://zedensix.com/homerun/docs/mcp · Site index: https://zedensix.com/llms.txt

# MCP server for AI agents

Homerun serves an [MCP](https://modelcontextprotocol.io) server, so an AI agent
such as Claude can diagnose and fix your services for you: read their logs,
deploys and settings, change them, redeploy, roll back.

| What           | Value                                                                      |
| -------------- | -------------------------------------------------------------------------- |
| MCP URL        | `https://<your dashboard>/api/v1/mcp`                                      |
| Transport      | Streamable HTTP                                                            |
| Sign-in        | OAuth 2.1 with PKCE, or an API key in the `x-api-key` header               |
| Client sign-up | Client ID Metadata Documents, Gemini's own registration, or **IDP**        |
| Acts as        | The account that signed in, or the API key's owner, with their permissions |

It only runs when the Dashboard URL is `https` (or plain HTTP on `localhost`),
since MCP clients refuse anything else; on any other address the endpoint
answers 404 saying so. The dashboard also has to be reachable from wherever the
agent runs: for claude.ai that's the public internet.

## Connecting an agent

An agent connects one of three ways: pick the first one it supports. Gemini has
its own way, see [Gemini](#gemini).

### Paste the URL (Client ID Metadata Documents)

An MCP client that supports
[Client ID Metadata Documents](https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/)
(CIMD, the client registration method of the MCP authorization spec) needs
nothing more than the MCP URL. Add it as a connector or server in the agent, and
the agent sends you to Homerun's sign-in page. Sign in, then Homerun asks
whether to allow it, and from then on it acts as you.

The client identifies itself with an HTTPS URL instead of a client ID, and
Homerun reads its name and callback addresses from the document at that URL. A
document can claim any name, so the consent screen also shows the site it came
from: only allow it when that's the site of the agent you're connecting. Once
allowed, the client shows up on the **IDP** page like any other app.

### Gemini

The Gemini app connects as a custom app: on
[gemini.google.com](https://gemini.google.com), open **Settings → Connected
Apps**, click **Add a custom app**, paste the MCP URL and leave **Advanced
features** empty. Gemini registers itself with Homerun, then sends you to
Homerun's sign-in page; sign in and allow it. From then on, type `@` in a chat
and pick it, on the web and in the mobile app. Google's own requirements apply
to the Gemini side (a personal Google account, Keep Activity on, and the
countries and languages custom apps are available in).

Registration is open to Gemini only: Homerun accepts it when every callback is
Google's relay for a Gemini custom app
(`https://oauth-redirect.googleusercontent.com/r/user_bound_custom-mcp-…`), and
stores it as a public app named Gemini, with PKCE and the consent screen always
on and no secret. Anyone with a Google account can register one, so the consent
screen is the gate: only allow a Gemini sign-in you started yourself. Each
registration shows up on the **IDP** page; switch one off there to cut it off.

Gemini asks before any tool that changes something; tools that only read are
marked as such.

### A client an admin registers (client ID and secret)

For an agent that asks for a client ID and secret instead, an admin registers
one first. On the **IDP** page (under Integrations), click **Register app**. For
claude.ai and Claude Desktop, click **Claude connector**: it fills in Claude's
callback URL, a confidential client with PKCE and the consent screen on. Then
click **Register app**, and the next screen shows the MCP URL, client ID and
secret. In Claude, open Settings → Connectors → Add custom connector, paste the
MCP URL, and the client ID and secret under Advanced settings.

The **Claude connector** button only shows once the Dashboard URL is `https` (or
plain HTTP on `localhost`).

### An API key (Claude Code and other headless agents)

No OAuth at all: create a key under **Profile → API Keys** and send it in the
`x-api-key` header. A key with read permissions only gives an agent that can
diagnose but not change anything.

```bash
claude mcp add --transport http homerun \
  https://<your dashboard>/api/v1/mcp --header "x-api-key: <key>"
```

## What it can do

| Area                    | Reads                                                                                                                                                                                                                   | Changes                                                                                                                                                                                                                                                                                                   |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Services                | `list_services`, `get_service`, `get_service_config`, `service_logs`, `search_service_logs` (logs kept across deploys), `list_deployments` (each deploy attempt's error and log), `list_revisions`, `list_dependencies` | `create_service` (from scratch or a template), `update_service`, `set_dependencies`, `deploy_service`, `restart_service`, `start_service`, `stop_service`, `rollback_service`                                                                                                                             |
| Errors                  | `list_errors`, `get_error`, `list_app_errors` (what Homerun logged about the service)                                                                                                                                   | `update_error` (resolve, ignore or reopen an issue)                                                                                                                                                                                                                                                       |
| Traces                  | `list_traces`, `get_trace` (a service's, or Homerun's own jobs' without a `serviceId`)                                                                                                                                  |                                                                                                                                                                                                                                                                                                           |
| Stacks and templates    | `list_stacks`, `get_stack`, `list_templates`, `get_template`                                                                                                                                                            | `create_stack`, `update_stack`                                                                                                                                                                                                                                                                            |
| Environments and canary | `list_environments`, `get_release_channels`                                                                                                                                                                             | `create_environment`, `update_environment`, `set_release_channels`                                                                                                                                                                                                                                        |
| Pull request previews   | `list_previews`, `get_preview`                                                                                                                                                                                          | `promote_preview`                                                                                                                                                                                                                                                                                         |
| Image scans             | `list_scans`, `get_scan` (with its findings)                                                                                                                                                                            | `scan_service`                                                                                                                                                                                                                                                                                            |
| Volumes and backups     | `list_volumes`, `list_volume_mounts`, `list_backups`, `list_backup_destinations`                                                                                                                                        | `create_volume`, `update_volume` (its backup settings too), `mount_volume`, `update_volume_mount`, `unmount_volume`, `run_backup`                                                                                                                                                                         |
| Cron jobs               | `list_cron_jobs`, `get_cron_job`, `list_cron_job_runs`, `get_cron_job_run` (a run's output)                                                                                                                             | `create_cron_job`, `update_cron_job` (`enabled` turns its schedule on or off), `delete_cron_job`, `run_cron_job`                                                                                                                                                                                          |
| Queue and instance      | `get_job`, `list_jobs`, `system_stats`, `instance_status`                                                                                                                                                               |                                                                                                                                                                                                                                                                                                           |
| Redirects               | `list_redirects`                                                                                                                                                                                                        | `create_redirect`, `update_redirect`, `delete_redirect`                                                                                                                                                                                                                                                   |
| Object storage          | `list_object_stores`, `list_buckets`, `get_bucket`                                                                                                                                                                      | `create_bucket`, `update_bucket` (expiry and public access), `delete_bucket` (empty buckets only)                                                                                                                                                                                                         |
| Status pages            | `list_status_pages`                                                                                                                                                                                                     | `create_status_page`, `update_status_page`, `delete_status_page`                                                                                                                                                                                                                                          |
| Notifications           | `list_notifications` (your own feed, the dashboard's bell), `list_notification_channels`                                                                                                                                | `mark_notifications_read`, `create_notification_channel`, `update_notification_channel`, `delete_notification_channel`                                                                                                                                                                                    |
| Workstations            | `list_workstations`, `get_workstation`, `list_workstation_presets`, `get_workstation_preset`                                                                                                                            | `create_workstation`, `update_workstation` (its login wall too), `start_workstation`, `stop_workstation`, `recreate_workstation`, `delete_workstation` (`deleteVolume` drops the home volume), `create_workstation_preset`, `update_workstation_preset` (its login wall too), `delete_workstation_preset` |
| Versions and updates    | `get_service_version` (an image service's pin and the update its last check found, with its trial run), `find_pin_tags`                                                                                                 | `pin_service`, `unpin_service`, `set_update_watch`, `check_service_updates`, `apply_service_update` (moves it onto the update and deploys)                                                                                                                                                                |
| Monitoring              | `get_monitoring` (the instance's traffic, availability and resources, per service), `get_service_monitoring` (with its latest uptime probe beats)                                                                       |                                                                                                                                                                                                                                                                                                           |
| System                  | `list_system_logs` (Homerun's own warnings and errors), `list_system_containers`, `system_container_logs` (Traefik's included), `preview_docker_cleanup`                                                                | `run_docker_cleanup` (every prune but volumes)                                                                                                                                                                                                                                                            |
| DNS                     | `list_domains`, `list_dns_records`, `list_dns_connections` (no credentials)                                                                                                                                             | `create_dns_record`, `update_dns_record`, `delete_dns_record`                                                                                                                                                                                                                                             |
| Docker registry         | `list_registry_repositories` (images with their tags, digests and the services using them), `list_registry_tags`, `get_registry_settings`, `list_registry_tokens`                                                       |                                                                                                                                                                                                                                                                                                           |
| Admin config            | `list_remote_hosts`, `list_users`, `list_git_providers`, all read only                                                                                                                                                  |                                                                                                                                                                                                                                                                                                           |

Deleting a service, a stack or an environment is deliberately not a tool: do it
from the dashboard. Neither are pruning volumes, deleting registry images, IaC
or updating Homerun. Workstations are the caller's own, like everywhere else,
and never come with their IDE password.

### Credentials and access stay on the dashboard

An agent reads admin config but never changes what holds a credential or grants
access: DNS connections, git providers, object stores, remote hosts, registry
tokens, users and sign-in. Those tools return metadata only (names, kinds,
endpoints, which secret fields are set), never a secret, and no tool creates,
changes or deletes them. Connecting one means pasting a secret into the
conversation, where it lands in the agent's context and its provider's logs; and
an agent able to swap DNS credentials, mint registry tokens or change a user's
role can widen its own reach, which a prompt injection in a log line or an error
it reads could ask for. Do those from the dashboard, the CLI, the REST API or
Terraform. DNS records are the one write here: they hold no secret, and creating
one is part of putting a service online.

Every tool goes through the REST API with the caller's own permissions, checked
per call: a read-only account, or an API key with read permissions, can diagnose
but not change anything. Any signed-in account can connect, so what an agent can
do is what its account can do; see [Users and roles](https://zedensix.com/homerun/docs/users-and-roles).

## Secrets

Secrets don't reach the agent, the rest stays readable:

- An env var of a service or a cron job marked secret by hand (see
  [Marking a variable secret](https://zedensix.com/homerun/docs/env-vars#marking-a-variable-secret)) shows as
  `[redacted]`, whatever its name or value.
- The same goes for an env var whose name has a secret-looking part (`PASSWORD`,
  `SECRET`, `TOKEN`, `KEY`, `AUTH`, `COOKIE`, `SIGNING`, `CREDENTIAL`,
  `PRIVATE`, `SALT`…), unless its last part only configures one
  (`OIDC_AUTH_URL`, `BASIC_AUTH_ENABLED`, `AUTH_PROVIDER`, `PRIVATE_KEY_FILE`)
  or its value is a plain boolean or number (`ENABLE_PASSWORD_FORM=false`).
- And so does a value that looks like a credential, whatever its name: a
  `user:token` pair with 16 or more letters and digits after the colon, or a
  standalone token of 24 or more characters mixing upper case, lower case and
  digits (not a URL, path, image, hostname or version).
- Any other value is shown as is, except its inline secrets: a URL's password
  (`postgres://app:[redacted]@db:5432/app`, `user:[redacted]@github.com`), a
  `--requirepass` or `--password` argument, a `Bearer` or `Basic` credential, a
  sensitive query parameter (`?code=`, `&state=`, `?token=`, `?api_key=`,
  `?X-Amz-Signature=`…) and a bare `token=`, `password=` or `api_key=`
  assignment. Logs get the same: service logs, deploy and job logs, the system
  log, a cron job's command and a cron job run's output.
- In a cron job's script source and its files, a line setting a secret-looking
  name (`API_TOKEN=…`, `export DB_PASSWORD=…`, `password: …`,
  `const apiKey = …`) has its value redacted, and every other line its inline
  secrets.
- An error event from [error tracking](https://zedensix.com/homerun/docs/error-tracking) has its request URL,
  query string and breadcrumb URLs and messages masked the same way, and its
  `Authorization`, `Cookie`, `Set-Cookie`, `X-Api-Key` and other
  credential-carrying headers and its cookies redacted.
- A service's uploaded icon shows as `uploaded` instead of its image data.

A workstation's and a workstation preset's `env` are redacted the same way, by
name and by value.

`update_service`, `update_cron_job`, `update_workstation` and
`update_workstation_preset` merge env changes into what's stored: send only the
vars to change, `null` deletes one. A var, a command, a cron job's source or one
of its files sent back exactly as the agent read it, a `[redacted]` value
included, stays unchanged, and so does an icon sent back as `uploaded`.

## Revoking access

Every OAuth client, registered by an admin or discovered from its URL, is on the
**IDP** page. From an app's page, revoke one person's tokens or everyone's, or
switch the app off: its tokens stop working and nobody can sign in through it
until it's back on. Switch off a discovered client rather than deleting it:
deleting one only lasts until it next signs someone in, since its URL is all it
needs to come back. An API key is revoked under **Profile → API Keys**.

## For client developers

Homerun follows the
[MCP authorization spec](https://modelcontextprotocol.io/specification/latest/basic/authorization):

- An unauthenticated call gets a `401` with
  `WWW-Authenticate: Bearer resource_metadata="https://<your dashboard>/.well-known/oauth-protected-resource/api/v1/mcp"`.
- That protected resource metadata names the authorization server, whose
  metadata is at
  `https://<your dashboard>/.well-known/oauth-authorization-server/issuer`
  (issuer `https://<your dashboard>/issuer`). It advertises
  `client_id_metadata_document_supported: true` and a `registration_endpoint`
  that only takes Gemini custom apps: dynamic client registration (RFC 7591)
  with any other callback is refused with `invalid_client_metadata`, and an
  accepted one is stored as a public client (`none`), so no client gets itself a
  secret.
- Authorization is the code flow with PKCE (`S256`). Pass the MCP URL as the
  `resource` parameter: the access token is a JWT whose audience is that URL,
  which is what the MCP endpoint checks. Gemini doesn't send one, so Homerun
  adds it to every authorization request coming back to a Gemini callback.
  Scopes: `openid`, `profile`, `email`, `offline_access` (for a refresh token)
  and `groups`.
- A metadata document's URL must be `https`, resolve only to public addresses (a
  private, loopback or link-local address is refused with `invalid_client`
  before anything is fetched), and answer without a redirect. The document needs
  `client_id` equal to its own URL, `client_name` and `redirect_uris`, and its
  `token_endpoint_auth_method` must be `none` or `private_key_jwt`: a shared
  secret makes no sense in a public document. Homerun keeps a fetched document
  for up to an hour, honouring its cache headers, then fetches it again.

## Troubleshooting

- **The MCP URL answers 404**: the Dashboard URL isn't `https`. Set it under
  **Settings → General**.
- **A connector says it can't authenticate**: the agent doesn't support metadata
  documents. Register a client for it, as in
  [A client an admin registers](#a-client-an-admin-registers-client-id-and-secret),
  and give it the client ID and secret.
- **`invalid_client` with "must not target a private or reserved address"**: the
  client's metadata document URL points at a private network. Agents on your LAN
  need an API key instead.
- **Tools answer 403**: the account (or the API key) lacks the permission that
  tool needs. The agent can only do what that account can do.
