LocalCan
officialGives AI agents public URLs (tunnels) for localhost, live HTTP traffic inspection, snapshot publishing, and access control.
What can you do with LocalCan MCP?
- Inspect captured traffic — Ask your assistant to list recent exchanges with
list_trafficor pull a full request/response viaget_exchangein markdown, curl, or HAR format. - Manage public tunnels — Create, pause, resume, or remove Public URLs with tools like
create_public_urlandpause_public_url, including setting custom request headers. - Publish and refresh snapshots — Deploy a folder as a shareable Snapshot with
publish_snapshot, then update it later withupdate_snapshotso preview links stay current. - Control access and comments — Password-protect a URL with
set_password, review comment threads vialist_comments, and reply or resolve them directly from your assistant. - Check tunnel and service status — Use
get_statusto confirm capture is running, orlist_public_urlsto see which links are live, paused, or serving snapshots.
Documentation
MCP server
Run LocalCan's Model Context Protocol server and wire it into your MCP host, with a full reference of the tools and switches.
localcan mcp runs a Model Context Protocol server over stdio. An MCP host (Claude Code, Codex, Cursor, Claude Desktop, and others) launches it and calls LocalCan's tools to read captured traffic, manage Public URLs (tunnels), and publish Snapshots. LocalCan must be running for the tools to return data, so open the desktop app or run localcan start -d first.
Tools
The server exposes twenty-six tools. Reading works out of the box. The sixteen tools that change things need write access, which is off by default (see the switches below). Creating or adding a Public URL needs an active license. Publishing a Snapshot and password-protecting a URL need a subscription plan, so a perpetual license is turned away even though it can still open Public URLs. Unlicensed, the gated tools return a clear activation message, while pausing, resuming, and removing existing URLs still work.
Traffic:
| Tool | What it does | Parameters |
|---|---|---|
get_status | Reports whether capture is on and how much traffic is buffered. | none |
enable_capture | Turns capture on. Capture is off by default and resets when the daemon restarts. | none |
list_traffic | Lists recent exchanges, newest first. | last (default 20), host substring, project id, method, status (exact code or a class like 5xx) |
get_exchange | Returns one exchange by id. | id required (full id or any unique prefix), format one of markdown, curl, http, har, json (default markdown), include_response (default true) |
An exchange is the request LocalCan forwarded to your backend, not a byte-for-byte copy of the client's original request. See Traffic for the data model.
Public URLs:
| Tool | What it does | Parameters |
|---|---|---|
list_services | Lists the services LocalCan serves, each with a <project>/<service> handle, its local target, and endpoint count. | none |
list_public_urls | Lists your Public URLs, including paused ones, each with its state (active, paused, error, starting, inactive) and what it serves (live, snapshot, none). Every row also carries access: none, password, link, or a team-policy name. A parked URL that serves a Snapshot reads state paused but serving snapshot, so answer "is the link up?" from serving, not state. | none |
get_public_url_status | Reports one Public URL's state, what it serves (live, snapshot, none), and its access protection, same vocabulary as the list, plus its local target and any request header rules. | url required |
create_public_url | Creates a Public URL for a local port in a new project and returns the assigned address, like my-app-12.localcan.dev. Takes a few seconds. If the tunnel is rejected (for example your plan's Public URL limit) or times out, the attempt is rolled back and nothing is left behind. For a link that stays reachable after your machine goes offline, add a Snapshot with add_snapshot. For an app served as a virtual host, pass host and a Host rule in headers (see below). | port required, name optional (shapes the address), protocol http or tcp (default http), host optional (defaults to localhost), headers optional (request header rules, each {name, value, mode?, enabled?}) |
add_public_url | Adds a Public URL to a service you already have configured. The protocol follows the service's target, so a tcp:// target gets a TCP tunnel. Same rollback on failure as create. | service handle required |
pause_public_url | Takes a Public URL offline while keeping its address, so it can be resumed later. A generated *.localcan.dev address stays reserved for 7 days while paused, custom domains never expire. | url required |
resume_public_url | Brings a paused Public URL back online at the same address. | url required |
remove_public_url | Permanently removes a Public URL. A generated address is released, a custom domain stays yours and can be added again. Removing a service's last endpoint also removes the emptied service and project. To keep the address but stop serving a Snapshot, use remove_snapshot. Marked destructive, so hosts typically ask for confirmation. | url required |
set_public_url_headers | Replaces the request header rules on a Public URL, the headers LocalCan sets before forwarding to your app. Pass the full list, an empty list clears them. get_public_url_status reports the rules in the same shape (mode set, append, or remove, and enabled), so a list read there can be edited and written back. | url and headers required |
An app served as a virtual host (a Laravel Herd or Valet site at myapp.test, an nginx server_name) needs to see its own hostname, and LocalCan forwards the public hostname by default. Pass host and a Host rule, headers: [{"name": "Host", "value": "{{target_host}}"}], and the app serves the right site. The value templates are the ones from Headers.
Snapshots (see Snapshots):
| Tool | What it does | Parameters |
|---|---|---|
publish_snapshot | Publishes a folder as a Snapshot on a new Public URL, so it stays reachable after your machine goes offline. Point it at built static output when you can, or at a project root for LocalCan to build (dependencies must already be installed). Returns the new address. Always creates a new URL, so to refresh an existing preview use update_snapshot. | path required (absolute), name optional (shapes the address) |
add_snapshot | Adds a Snapshot to a Public URL you already have, so an existing link keeps serving offline. Points at update_snapshot if the URL already has one. | url and path required |
update_snapshot | Re-publishes the Snapshot on a Public URL. Omit path to rebuild from the same source, or pass it to repoint at another folder. Points at add_snapshot if the URL has none. | url required, path optional |
remove_snapshot | Removes the Snapshot from a Public URL. The URL stays reserved and keeps serving live while your tunnel is up. Marked destructive. | url required |
get_snapshot_status | Reports a Public URL's Snapshot: its source folder, when it was published, whether the source changed since (stale), and whether the URL serves live or the snapshot right now. Also carries the review comments on it (state and counts) and, once comments have been on, the Snapshot's version number. | url required |
Access control (see Access control):
| Tool | What it does | Parameters |
|---|---|---|
set_password | Password-protects a Public URL so only people who have the password can open it. Enforced on LocalCan's servers, so it also covers a Snapshot on that URL. Generates a strong password unless you pass one, and returns it so you can share it. Needs a subscription plan. | url required, password optional (omit to generate one) |
clear_access | Removes password protection, making the URL public again. Does not remove the URL or its Snapshot. Marked destructive, so hosts typically ask for confirmation. | url required |
get_access_status | Reports a Public URL's protection and returns its current password when it is password-protected. The password is never returned by list_public_urls, only here. | url required |
Comments (the review comments reviewers leave on a Snapshot, see Comments):
| Tool | What it does | Parameters |
|---|---|---|
list_comments | Lists the comment threads on a Public URL's Snapshot with their replies. Each thread carries the page path, the anchor (a CSS selector and the pin's position in that element), the reviewer's viewport and browser, and the Snapshot version it was left on. Never marks anything read. | url required, status open, resolved, or all (default open), page path, version number |
reply_comment | Posts a reply into a thread under your account's name. Reviewers on the thread get it by email unless reply notifications are off for the team or they unsubscribed. Replies only, new threads are pinned on the page. | url, comment_id, body required |
resolve_comment | Marks a thread resolved, replies included. | url and comment_id required |
reopen_comment | Reopens a resolved thread. | url and comment_id required |
set_comments | Switches comments on a Snapshot: on, paused (existing threads stay readable, no new ones), or off. Needs a protected URL and a subscription plan. | url and state required |
The feedback loop
The tools chain into one loop an agent can run on its own: list_comments to read the open threads, edit the source, update_snapshot to publish the new version, then reply_comment and resolve_comment per thread. Comments carry over to the new version, so the reviewer sees the reply on the same pin. The server tells the agent this itself. Its MCP instructions, which hosts add to the agent's prompt, describe the loop, the review-round setup (publish_snapshot, set_password, set_comments), and the virtual host recipe. Two things an agent cannot do: start a thread (reviewers pin those on the page) and mark threads read (unread is your own inbox state in the app).
Connecting an agent
How you connect depends on how the agent runs. Terminal agents (Claude Code, Codex) inherit your shell PATH, so a bare localcan command works. GUI apps (Cursor, Claude Desktop, VS Code, and others) do not load your shell PATH, so they need the absolute path to the binary, for example /Users/you/.localcan/bin/localcan. The desktop app's Settings can copy a ready-made config with the right path filled in, which is also the reliable route on Windows.
Claude Code
claude mcp add --scope user localcan -- localcan mcp
Flag --scope user registers the server for every project. Drop it to register it only in the current project.
Codex
codex mcp add localcan -- localcan mcp
This writes the server to ~/.codex/config.toml. For the Codex desktop app or IDE extension, pass the absolute path in place of localcan.
Cursor, Claude Desktop, and Windsurf
These share the same mcpServers format:
{
"mcpServers": {
"localcan": {
"command": "/Users/you/.localcan/bin/localcan",
"args": ["mcp"]
}
}
}
Add it to the right file, then reload:
- Cursor:
~/.cursor/mcp.json, then enable the server in Settings. - Claude Desktop:
claude_desktop_config.json(Settings, Developer, Edit Config), then quit and relaunch. - Windsurf:
~/.codeium/windsurf/mcp_config.json, then refresh the MCP panel.
VS Code
VS Code (Copilot agent mode) uses a servers key with an explicit type. Add this to .vscode/mcp.json in your workspace:
{
"servers": {
"localcan": {
"type": "stdio",
"command": "/Users/you/.localcan/bin/localcan",
"args": ["mcp"]
}
}
}
You can also run code --add-mcp with the same server object.
Zed
Zed uses context_servers in its settings.json:
{
"context_servers": {
"localcan": {
"source": "custom",
"command": "/Users/you/.localcan/bin/localcan",
"args": ["mcp"]
}
}
}
You can also add it from the Agent Panel settings.
Agent access, redaction, and write access
All three are controlled in the desktop app under Settings (the "AI Agents (MCP)" section), or from the terminal: localcan mcp enable / disable for agent access, localcan mcp redact <on|off> for redaction, localcan mcp access <read_only|read_write> for write access, and localcan mcp status to see the current state.
- Agent access is on by default. Turn it off to stop agents from using LocalCan at all. The server still starts, but every tool returns a clear "access is disabled" message until you turn it back on.
- Redaction is on by default for agents. Sensitive headers (Authorization, cookies, API keys) are removed from tool responses. URLs and bodies are not redacted. Turn it off to let your own agent receive raw values.
- Write access is off by default. Reading works without it, but the write tools return a clear read-only message until you turn it on, in the app ("Allow agents to create and change Public URLs") or with
localcan mcp access read_write. Turning on agent access does not grant write access. They are separate switches. Every write call is logged to the server's diagnostic output, which your host captures, so you have a record of what an agent changed. A password passed toset_passwordis masked in that log.
When a tool refuses
- Every tool errors with a daemon connection message: LocalCan is not running. Open the desktop app or run
localcan start -d. list_trafficreturns nothing: capture is off (it is off by default and resets when the daemon restarts). Runlocalcan traffic enableor let the agent callenable_capture.- "MCP access is disabled": agent access is switched off. Run
localcan mcp enableor flip the Settings toggle. - "MCP is read-only": the tool changes things and write access is off. Run
localcan mcp access read_writeor turn on the Settings toggle. - "public URLs require a license": creating and adding a Public URL need an active license. Activate one in the app or with
localcan license activate <key>. - "need a subscription plan": Snapshots and Access control are subscription-only. A perpetual license can open Public URLs but cannot publish a Snapshot or set a password. Subscribe from your dashboard, then retry.
- "already has a snapshot" or "has no snapshot yet": use the tool the message names.
add_snapshotattaches a Snapshot to a URL that has none,update_snapshotrefreshes one that already does. - "Snapshot limit reached": your plan caps how many Public URLs can serve a Snapshot at once. The message lists the URLs already using a slot, which you can refresh with
update_snapshotinstead of publishing a new one. - "Comments need a protected URL":
set_commentswas called on a URL without Access control. Runset_passwordfirst. - "Your account has no display name": a reply needs a name to post under. Set it in the dashboard, or reply once on the Snapshot page after opening it as owner from the app.
- The host shows the server as failed or with no tools: a GUI app cannot find
localcanon the PATH. Use the absolute path, easiest via the Settings copy config.