Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# The Dockerfile downloads a release and copies nothing from the repository.
*
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ jobs:
CORE: '^(core/|skills/|\.github/workflows/ci\.yml$)'
MACOS: '^(apps/macos/|scripts/build-core\.sh$)'
DESKTOP: '^(apps/desktop/|scripts/(test-updater-scripts|updater-manifest|appcast|sparkle-public-key)\.sh$)'
# The package's tests read the skill and the site's install links.
CLI: '^(packages/cli/|skills/|site/src/lib/(site|release)\.ts$|site/src/components/InstallDialog\.astro$|site/astro\.config\.mjs$|\.github/workflows/ci\.yml$)'
# The package's tests read the skill, the site's install links and the MCP Registry's server.json.
CLI: '^(packages/cli/|skills/|server\.json$|\.mcp\.json$|site/src/lib/(site|release)\.ts$|site/src/components/InstallDialog\.astro$|site/astro\.config\.mjs$|\.github/workflows/ci\.yml$)'
run: |
if [[ $GITHUB_EVENT_NAME == pull_request ]]; then
files=$(git diff --name-only HEAD^1 HEAD)
Expand Down
8 changes: 8 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"localhost": {
"command": "npx",
"args": ["-y", "@getlocalhost/cli", "mcp", "serve"]
}
}
}
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ apps/macos/ the macOS app
apps/desktop/ the Linux and Windows app (Tauri 2)
src/ React + TypeScript UI; src/bindings.ts is generated, never edited
src-tauri/ Rust shell hosting the core
packages/cli/ `npx @getlocalhost/cli mcp install`: connects agents from a terminal (Node, no dependencies)
packages/cli/ `npx @getlocalhost/cli mcp install`: connects agents from a terminal; `mcp serve` runs the app's server (Node, no dependencies)
skills/ agent skills, e.g. using localhost's MCP server
site/ the website (Astro): landing page, docs, changelog; reads docs/ and the desktop tokens
scripts/ build-core.sh, build-release.sh, signing and installer artwork
packaging/homebrew/ the Homebrew cask's template; the tap is marcellosso/homebrew-tap
assets/ brand images for listings, e.g. the 400×400 logo
docs/configuration.md the Project file format (user-facing)
server.json, glama.json MCP directory listings, with the example .mcp.json and the introspection-only Dockerfile
```

## Vocabulary
Expand Down
16 changes: 14 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ Checking the app for Windows from macOS or Linux works like the core's check abo
2. Run the checks for what you touched (AGENTS.md, Verifying a change), then build and run the app.
3. Open a PR describing what changed and why, how you checked it, and anything you left out. Link the issue it addresses.

CI runs the core's tests on Linux, macOS and Windows, the Swift layer's tests, an unsigned app build, and the desktop app's checks (TypeScript, Vitest, clippy, its Rust tests, up-to-date bindings), and the npm package's tests; all must pass. On a PR each job runs only when files it depends on changed, and is skipped otherwise: the core's jobs for `core/` and `skills/` (the core embeds the localhost-mcp skill), the macOS job for those and `apps/macos/`, the desktop job for those and `apps/desktop/`, the npm package's (`packages/cli/`, on Linux, macOS and Windows) for it, `skills/` and the site's install links, and each for the scripts it runs. The site builds only for `site/`, `docs/`, `.github/assets/` and the desktop tokens, and Vercel's preview of a PR follows the same paths (`site/scripts/ignore-build.sh`), so other PRs show it as Ignored; production on `main` and the release's deploy hook always build. Changing a workflow runs everything in it, and every push to `main` runs everything; release packages are built only by a release tag, and the npm package is published only by running its workflow by hand. A skipped job counts as passing, and the `CI result` check fails if any CI job failed. The macOS job also skips draft PRs (macOS minutes cost the most), so mark yours ready for review when you want the full run. CI on a PR from a fork starts once a maintainer approves the run, and every PR needs a maintainer's approving review before it merges. Commits are kept as written (merge commits, no squash), so make each commit message say what it does in one line.
CI runs the core's tests on Linux, macOS and Windows, the Swift layer's tests, an unsigned app build, and the desktop app's checks (TypeScript, Vitest, clippy, its Rust tests, up-to-date bindings), and the npm package's tests; all must pass. On a PR each job runs only when files it depends on changed, and is skipped otherwise: the core's jobs for `core/` and `skills/` (the core embeds the localhost-mcp skill), the macOS job for those and `apps/macos/`, the desktop job for those and `apps/desktop/`, the npm package's (`packages/cli/`, on Linux, macOS and Windows) for it, `skills/`, the root `server.json` and `.mcp.json`, and the site's install links, and each for the scripts it runs. The site builds only for `site/`, `docs/`, `.github/assets/` and the desktop tokens, and Vercel's preview of a PR follows the same paths (`site/scripts/ignore-build.sh`), so other PRs show it as Ignored; production on `main` and the release's deploy hook always build. Changing a workflow runs everything in it, and every push to `main` runs everything; release packages are built only by a release tag, and the npm package is published only by running its workflow by hand. A skipped job counts as passing, and the `CI result` check fails if any CI job failed. The macOS job also skips draft PRs (macOS minutes cost the most), so mark yours ready for review when you want the full run. CI on a PR from a fork starts once a maintainer approves the run, and every PR needs a maintainer's approving review before it merges. Commits are kept as written (merge commits, no squash), so make each commit message say what it does in one line.

Releases are cut by tagging `main`; see [RELEASING.md](RELEASING.md).

Expand All @@ -130,7 +130,7 @@ By contributing, you agree your contributions are licensed under the MIT license

`@getlocalhost/cli` (`packages/cli/`) is published to npm from CI by the [Publish the npm package](.github/workflows/publish-cli.yml) workflow, run by hand. npm trusts that workflow through trusted publishing, so there is no npm token. With each app release:

1. Bump `version` in `packages/cli/package.json` and merge it into `main`. npm never accepts a version twice.
1. Bump `version` in `packages/cli/package.json`, and both versions in the root `server.json` to match (the package's tests check it), and merge it into `main`. npm never accepts a version twice.
2. Run the workflow on `main`, first as a dry run:

```sh
Expand All @@ -139,3 +139,15 @@ By contributing, you agree your contributions are licensed under the MIT license
```

It runs the package's tests, fails if the version is already on npm, checks the package includes the skill, and publishes it with provenance. A dry run does all but the publishing, from any branch; publishing runs only from `main`.

## Publishing to the MCP Registry

The root `server.json` lists `@getlocalhost/cli` in the [MCP Registry](https://registry.modelcontextprotocol.io) as `io.github.marcellosso/localhost`, run as `npx -y @getlocalhost/cli mcp serve`. The registry checks that npm has that version and that its `package.json` names the server in `mcpName`, so publish it after the npm package, with [`mcp-publisher`](https://github.com/modelcontextprotocol/registry/releases) (`brew install mcp-publisher`):

```sh
mcp-publisher validate
mcp-publisher login github # as marcellosso, who owns the io.github.marcellosso namespace
mcp-publisher publish
```

The other files at the root are for MCP directories too: `glama.json` names the maintainers for Glama, the `Dockerfile` lets directories that inspect a server's tools run `localhost mcp` from the latest release, and `.mcp.json` is the example configuration. Claude Code reads that `.mcp.json` as a project server: it asks before using it, and once allowed it replaces a user-scope `localhost` server of the same name in this repository. Decline it, or list it in `disabledMcpjsonServers` in `.claude/settings.local.json`, to keep the app's own entry.
29 changes: 29 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# For MCP directories' introspection only, such as Glama's: it runs
# `localhost mcp` so they can list its tools. Inside a container it sees no
# Projects and can't start or stop anything on your computer: for real use,
# install the app (https://www.getlocalhost.app) and connect your agents with
# `npx -y @getlocalhost/cli mcp install`.
#
# The `localhost` command from release VERSION's .deb; VERSION=latest takes the latest release:
# docker build --platform=linux/amd64 -t localhost-mcp .
# docker build --platform=linux/amd64 --build-arg VERSION=latest -t localhost-mcp .
# The Linux app is amd64 only.
#
# Bump VERSION with each release, so directories list that release's tools.
FROM debian:bookworm-slim

ARG VERSION=0.4.0
RUN set -eu; \
apt-get update; \
apt-get install -y --no-install-recommends curl ca-certificates; \
if [ "$VERSION" = latest ]; then \
VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/marcellosso/localhost/releases/latest | sed 's|.*/tag/v||'); \
fi; \
curl -fsSL -o /tmp/localhost.deb "https://github.com/marcellosso/localhost/releases/download/v${VERSION}/localhost-app_${VERSION}_amd64.deb"; \
dpkg-deb -x /tmp/localhost.deb /tmp/localhost; \
install -m 0755 /tmp/localhost/usr/bin/localhost /usr/local/bin/localhost; \
localhost --version; \
apt-get purge -y --auto-remove curl; \
rm -rf /tmp/localhost /tmp/localhost.deb /var/lib/apt/lists/*

ENTRYPOINT ["localhost", "mcp"]
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,46 @@ It writes the same entries as **Connect** for every agent it finds, and Claude D

Other MCP clients take the same `command` and `args`. On Windows it covers Projects on Windows itself; a Project in a WSL distro runs only from the app, and agents installed inside a WSL distro are set up by hand. The skill is in [skills/localhost-mcp/SKILL.md](skills/localhost-mcp/SKILL.md): copy the `localhost-mcp` folder to `~/.claude/skills/`, `~/.cursor/skills/` or `~/.agents/skills/`. More in the [MCP docs](https://www.getlocalhost.app/docs/mcp), the [skill docs](https://www.getlocalhost.app/docs/skills) and [localhost for AI coding agents](https://www.getlocalhost.app/agents).

### How do I add the localhost MCP server to Claude Code, Cursor or Codex?

localhost app's MCP server, `localhost mcp`, is a stdio Model Context Protocol server that lets Claude Code, Cursor and Codex start, stop and read the logs of your local dev servers on macOS, Windows and Linux.

`npx -y @getlocalhost/cli mcp serve` runs `localhost mcp` from any MCP client's configuration: it finds the command that comes with the app and hands it the client's stdin and stdout (`mcp serve --read-only` runs `localhost mcp --read-only`). It needs the localhost app: install it from [getlocalhost.app](https://www.getlocalhost.app). Without it, the server exits and says on stderr how to install it.

Connect every agent on this computer at once:

```sh
npx -y @getlocalhost/cli mcp install
npx -y @getlocalhost/cli mcp install --read-only # without the tools that add, edit or remove
```

Or add the server to one agent:

- **Claude Code**: `claude mcp add --scope user localhost -- npx -y @getlocalhost/cli mcp serve`
- **Codex**: `codex mcp add localhost -- npx -y @getlocalhost/cli mcp serve`, or in `~/.codex/config.toml`:

```toml
[mcp_servers.localhost]
command = "npx"
args = ["-y", "@getlocalhost/cli", "mcp", "serve"]
```

- **Cursor, Claude Desktop and other clients**: in `~/.cursor/mcp.json` (or `.cursor/mcp.json` in one project), `claude_desktop_config.json`, or the client's own file:

```json
{
"mcpServers": {
"localhost": { "command": "npx", "args": ["-y", "@getlocalhost/cli", "mcp", "serve"] }
}
}
```

Tools: `list_projects`, `list_services`, `status`, `start_service`, `stop_service`, `start_project`, `stop_project`, `logs`, `ports`, `create_project`, `update_project`, `delete_project`, `create_service`, `update_service` and `delete_service`; `--read-only` leaves out the last six. The six that add, edit or remove need the localhost app 0.4.0 or later.

[`server.json`](server.json) describes this server for the [MCP Registry](https://registry.modelcontextprotocol.io), and [`.mcp.json`](.mcp.json) is the configuration above. The [`Dockerfile`](Dockerfile) is only for MCP directories that inspect a server's tools: in a container, `localhost mcp` sees no Projects and can't start or stop anything on your computer.

<!-- mcp-name: io.github.marcellosso/localhost -->

## FAQ

### What is localhost app?
Expand Down
Binary file added assets/logo-400.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions glama.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://glama.ai/mcp/schemas/server.json",
"maintainers": ["marcellosso"]
}
17 changes: 16 additions & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,31 @@ It writes what **Settings → Agents** in the app writes, and the app shows thes
npx @getlocalhost/cli mcp install # add the server and the skill
npx @getlocalhost/cli mcp uninstall # remove them, and nothing else
npx @getlocalhost/cli mcp status # where each agent stands
npx @getlocalhost/cli mcp serve # run the server, for an MCP client's configuration
```

| Option | |
|---|---|
| `--client <agent>` | `claude-code`, `claude-desktop`, `cursor` or `codex`; `claude` for both Claude apps. Repeat it, or separate with commas, for more than one. Without it: every agent found on this computer. |
| `--read-only` | The server runs `localhost mcp --read-only`, without the tools that add, edit or remove Projects and Services. Needs a localhost app that has it (from the release after 0.3.0). Installing again without it keeps a read-only server read-only. |
| `--read-only` | For `mcp install` and `mcp serve`. The server runs `localhost mcp --read-only`, without the tools that add, edit or remove Projects and Services. Needs a localhost app that has it (0.4.0 or later). Installing again without it keeps a read-only server read-only. |
| `--dry-run` | Prints what would change, as a diff, and changes nothing. |

Then start a new Claude Code session, quit and reopen Claude Desktop, check **Cursor Settings → MCP**, or restart Codex.

## Any MCP client

`mcp serve` is the server itself, for any client that starts MCP servers over stdio:

```json
{
"mcpServers": {
"localhost": { "command": "npx", "args": ["-y", "@getlocalhost/cli", "mcp", "serve"] }
}
}
```

It finds the app's `localhost` command as `mcp install` does and runs `localhost mcp` with the client's stdin and stdout, passing on Ctrl-C and other signals and exiting as the server does. `mcp serve --read-only` runs `localhost mcp --read-only`. It writes nothing to stdout itself, since that is the client's channel: without the app, or with an app whose command doesn't run, it says why on stderr and exits with 1. `claude mcp add --scope user localhost -- npx -y @getlocalhost/cli mcp serve` and `codex mcp add localhost -- npx -y @getlocalhost/cli mcp serve` add it from a terminal.

More in the [MCP docs](https://www.getlocalhost.app/docs/mcp). Node 18 or later; no dependencies.

## Development
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/bin/getlocalhost.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env node
import { main } from '../src/cli.js';

process.exitCode = main(process.argv.slice(2));
process.exitCode = await main(process.argv.slice(2));
5 changes: 3 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"name": "@getlocalhost/cli",
"version": "0.1.0",
"description": "Connect Claude Code, Claude Desktop, Cursor and Codex to localhost mcp, the MCP server of the localhost app, from a terminal.",
"version": "0.2.0",
"mcpName": "io.github.marcellosso/localhost",
"description": "Connect Claude Code, Claude Desktop, Cursor and Codex to localhost mcp, the MCP server of the localhost app, from a terminal, or run it from any MCP client with npx.",
"keywords": [
"localhost",
"mcp",
Expand Down
Loading
Loading