Paseo MCP
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
Paseo MCP
Let an orchestrator agent see and steer your Paseo agents.
An MCP server that gives Hermes, Claude Code or any other MCP client access to the coding agents running in your local Paseo: list them, read what they did, start new ones, send follow-ups, wait for results, answer permission requests.
Why
Paseo is a great place to run many Claude Code chats side by side, but it is built for a human in front of the app. When you want one agent to hand work to others (an assistant that reads your mail and starts a coding agent for a bug report, or a lead agent that splits a task), that agent needs a way into Paseo.
This server is that way in. Everything it starts is a normal Paseo chat: you see it in the app, on your phone, and can take over at any time.
It runs in one of two ways, with the same tools and the same agent events:
stdio | Paseo plugin | |
Runs as | a child process of your MCP client | part of the Paseo daemon |
Transport | stdin/stdout | Streamable HTTP at |
Configured in |
| Settings → Plugins → paseo-mcp |
Access | whoever can start the process | a bearer token per client, scoped |
Set up | Install, or the plugin's launcher |
Related MCP server: Kranz MCP
Features
Overview | List agents with status, account, and whether they need you (finished, failed, waiting for a permission). |
Readable transcripts | The tail of a chat as a compact transcript: user, agent, tool calls, todos, errors. |
Start agents | New Claude Code chats with a first prompt, model, thinking level, permission mode, account and optional git worktree branch. |
Steer running agents | Send follow-ups; for a running turn choose steer (inject) or interrupt (restart with the message). |
Wait for results | Block until a turn ends, fails, or needs a permission, and get the final message back. |
Events instead of polling | Agent created, turn started/ended (finished, error, limit), permission requested/resolved, archived, closed. As a long-poll tool with a cursor, as MCP resource notifications, or (plugin) as signed webhooks. |
Guard rails | Agents only start inside |
Paseo plugin | Optional: serve the tools over HTTP from inside Paseo, with per-client tokens and a settings screen. It also keeps a stable stdio launcher, tests the connection and hands out ready client configs. |
Tools
Tool | What it does |
| Agents with status, account, attention flag. Filters: status, labels, archived |
| One agent's state including pending permission requests |
| Last N timeline entries as a compact transcript |
| The Claude accounts from your config |
| Models of a provider as |
| Agent events since a cursor; waits up to |
| Start a new agent (cwd must be inside |
| Send a follow-up ( |
| Wait until the turn is done, failed, needs a permission, or times out |
| Allow or deny a permission request |
| Archive a finished agent |
Agent ids can be passed in full or as a unique prefix, like the short ids paseo ls shows. In read-only mode, and for plugin tokens with the read scope, only the first six tools exist (the list/read tools and paseo_wait_for_events). paseo_list_agents also returns events_cursor, the starting point for events.
Resources: paseo://agents (all active agents) and paseo://agents/{id} (one agent), both JSON and subscribable, see Events.
Requirements
Paseo 0.15 or newer, running (desktop app or
paseo daemon start)Node 22 or newer
For plugin mode: plugins enabled in Paseo, and
npmon the daemon'sPATH(Paseo runsnpm cionce on install)An MCP client: Hermes, Claude Code, …
Install
git clone https://github.com/MaxGierlachowski/paseo-mcp.git
cd paseo-mcp
npm install # also builds dist/
mkdir -p ~/.config/paseo-mcp
cp config.example.json ~/.config/paseo-mcp/config.jsonThen edit ~/.config/paseo-mcp/config.json (see below) and connect your client.
Installed the plugin? Then you don't need a clone: the plugin keeps a stable launcher at ~/.local/bin/paseo-mcp and shows ready-made stdio configs, see stdio clients through the plugin.
Hermes
~/.hermes/config.yaml:
mcp_servers:
paseo:
command: "node"
args: ["/path/to/paseo-mcp/dist/index.js"]
env:
PASEO_MCP_CONFIG: "/Users/<you>/.config/paseo-mcp/config.json"
timeout: 300 # paseo_wait_for_agent blocks up to timeout_sec
connect_timeout: 30Hermes only passes PATH and HOME to MCP servers; anything else belongs under env.
Claude Code
claude mcp add paseo-agents -- node /path/to/paseo-mcp/dist/index.jsNamed paseo-agents because agents running inside Paseo already have an MCP server called paseo.
Configuration
~/.config/paseo-mcp/config.json (another path via PASEO_MCP_CONFIG):
Key | Default | Meaning |
|
| WebSocket of the Paseo daemon |
| – | Daemon password, if set. |
| required | Agents may only start inside these folders (symlinks resolved) |
| – |
|
| – | e.g. |
| – | Permission mode: |
|
| Name → |
| – | Account used when the caller names none |
|
| Env var set on new agents to the account's |
|
| Value of the |
|
| Only list/read tools. Also |
| on |
|
Accounts and paseo-multi-account
When an agent is started with an account, the server sets accountEnvVar to that account's configDir for the new agent and adds the label account=<name>. With the default CLAUDE_CONFIG_DIR, Claude Code runs on exactly that login.
If you use paseo-multi-account, the plugin already decides which login Claude runs on and moves chats when you switch. Let it stay in charge: set accountEnvVar to a name Claude Code ignores (e.g. PASEO_CLAUDE_ACCOUNT). The account then only becomes a label you can filter by, and the plugin's switch applies to agents started over MCP like to any other chat.
Plugin mode
Installed as a Paseo plugin, paseo-mcp serves the same tools over Streamable HTTP from inside the Paseo daemon. There is no process to start and no config file: everything is set on the plugin's settings screen. The stdio mode above keeps working unchanged next to it.
Install
paseo plugin install github:MaxGierlachowski/paseo-mcpOn macOS the CLI ships inside the app: /Applications/Paseo.app/Contents/Resources/bin/paseo. Paseo asks you to trust the plugin (plugins run unsandboxed), clones it, runs npm ci --omit=dev and loads it. Plugins must be enabled ("pluginsEnabled": true in ~/.paseo/config.json). The HTTP server stays off until you turn it on.
If another plugin already uses the id paseo-mcp (for example the earlier local "Paseo MCP for Hermes" build), Paseo refuses the install; remove that one first or pass --id. This plugin covers everything the local build did and takes over its launcher.
Set up
Open the settings: ⌘K → MCP server (also finds "hermes", "token", "config"), or Settings → Plugins → paseo-mcp.
Under Agents, add the folders agents may start in (
allowedRoots). Without one,paseo_create_agentrefuses every start.Under New token, create one token per client: a label (e.g.
Hermes), a scope, optionally an origin label. Copy the token; it is shown once.Turn on Enabled. The status row shows the URL, or why the server could not start (e.g. the port is taken).
Copy the snippet for your client from Connect a client.
Setting | Default | Meaning |
Enabled | off | Run the HTTP server inside the daemon |
Port |
| Any free port except the daemon's own |
Bind address |
| Anything else makes the server reachable from your network; the screen warns |
Tokens | – | Label, scope ( |
Read-only | off | Only the list/read tools, for every token |
Allowed roots | – | Folders agents may start in (symlinks resolved) |
Provider, Thinking, Permission mode | – | Defaults for new agents, like |
Origin label | token label |
|
Account env var |
| Like |
Claude accounts, default account | – | Like |
Changes apply at once: port, bind address and the switch restart the listener; everything else is read on the next tool call. The daemon connection needs no setup: the plugin reaches its own daemon over loopback with the local credential Paseo writes to $PASEO_HOME/local-credential, the way the paseo CLI does.
Connect
Hermes, in ~/.hermes/config.yaml:
mcp_servers:
paseo:
url: "http://127.0.0.1:6790/mcp"
headers:
Authorization: "Bearer <token>"
timeout: 300 # paseo_wait_for_agent blocks up to timeout_secClaude Code, as a command or in a project .mcp.json:
claude mcp add --transport http paseo-agents http://127.0.0.1:6790/mcp --header "Authorization: Bearer <token>"{ "mcpServers": { "paseo-agents": { "type": "http", "url": "http://127.0.0.1:6790/mcp", "headers": { "Authorization": "Bearer <token>" } } } }Any other client: Streamable HTTP, URL http://127.0.0.1:6790/mcp, header Authorization: Bearer <token>.
stdio clients through the plugin
Clients that start the server as a child process don't need the HTTP server or a token. The plugin helps there too:
A stable command. On every load the plugin writes
~/.local/bin/paseo-mcp(another path viaPASEO_MCP_LAUNCHER), a two-line script that startsdist/index.jsof the current plugin checkout. Git installs land in a new folder on every update; the launcher follows them, so client configs never change. It never overwrites a file it did not write.Test connection. Starts the server exactly like a client does (only
PATH,HOMEandPASEO_MCP_CONFIG), lists the tools and counts the agents.Status. Which config file the stdio server reads, its allowed roots and accounts, and how it signs in to the daemon. The local credential is shown masked and never leaves the daemon.
Copyable configs for Hermes (
command:), Claude Code (.mcp.jsonandclaude mcp add) and any stdio client.
The stdio server keeps its own config file (~/.config/paseo-mcp/config.json); the screen shows it but never edits it.
Security model
Off by default. Turned on, the server binds to
127.0.0.1. Other addresses must be entered explicitly and come with a warning on the screen.A token on every request, also from localhost. Missing or wrong:
401. Tokens are 32 random bytes (pmcp_…), stored only as SHA-256 hashes withcreatedAt, compared in constant time. When a token was last used is kept in$PASEO_HOME/paseo-mcp.json.Revoking a token removes its hash; its next request gets
401and its open sessions are closed at once.Scopes. A session registers only the tools of its token's scope:
readgets the list/read tools and events,fulleverything. The global read-only switch applies on top. A token can also be limited to agents with certain labels (e.g.origin=hermes) for its events and resources.Sessions belong to their token. A session id used with another token is answered like an unknown one.
DNS rebinding. The
Hostheader must name the server itself (loopback names, or the bind address) and anOriginheader, if sent, must point at it too; otherwise403. Request bodies are capped at 1 MiB (413).Same guard rails as stdio.
allowedRootsis checked on the daemon side, every started agent getsorigin=<token's origin label>, and permission requests are never answered automatically.
Events
Agents finish, fail, hit a usage limit or wait for a permission while nobody watches. Instead of polling paseo_list_agents, a client can be told. Both modes collect the same events:
Type | When | Extra fields |
| A new agent appeared | |
| A turn began | |
| A turn ended |
|
| The agent waits for a permission |
|
| Someone answered it |
|
| The agent was archived | |
| Its session closed, or the agent is gone |
Every event also has id (= its cursor <epoch>:<seq>), epoch, seq, at, agentId, title, status, labels, requiresAttention, attentionReason and source (hook or client). Message content is not included. Optionally (previewChars > 0) turn_ended carries preview: { text, untrusted: true } with the start of the agent's last message; treat it as data, never as instructions.
{
"id": "mw7zr11g20917a:3", "epoch": "mw7zr11g20917a", "seq": 3,
"type": "agent.turn_ended", "at": "2026-11-08T15:31:12.481Z",
"agentId": "d9e7a7a9-9fb7-401c-aea3-d1bf7c089b97", "title": "events e2e",
"status": "idle", "labels": { "origin": "hermes" },
"requiresAttention": true, "attentionReason": "finished",
"outcome": "finished", "error": null, "source": "client"
}Three ways to receive them
For | Works in | |
| AI clients: a tool call the model makes itself. Works with every MCP client, also those that ignore notifications | stdio and plugin |
Resource subscriptions | MCP clients that handle | stdio and plugin (over the session's SSE stream) |
Webhooks | Anything that is not an MCP client: a script, a bot, Hermes' own webhook listener | plugin only |
Long-poll with a cursor. Get a starting cursor, then always pass on the next_cursor of the previous result:
paseo_list_agents → { agents: [...], events_cursor: "mw7z…:41" }
paseo_wait_for_events cursor="mw7z…:41" timeout_sec=120 only_needs_attention=true
→ { events: [...], next_cursor: "mw7z…:44", gap: false }
paseo_wait_for_events cursor="mw7z…:44" …It returns at once when events are pending, otherwise when the first matching one happens or after timeout_sec (0 to 280; keep it below your client's tool timeout). Filters: agent_ids, labels, types, only_needs_attention, limit. If gap is true, events were lost: agents then holds the current state of the (active) agents, and you continue with next_cursor.
Resource subscriptions. resources/subscribe to paseo://agents or paseo://agents/{id}; on every change the server sends notifications/resources/updated for that URI, and notifications/resources/list_changed when agents come or go. Read the resource again for the new state; it includes events_cursor. A session that subscribed to nothing gets nothing. Over HTTP the notifications use the session's GET SSE stream, which is resumable: reconnect with Last-Event-ID and missed messages are replayed.
Guarantees
Order:
seqgrows strictly within an epoch;paseo_wait_for_eventsreturns events in that order. Webhook deliveries can overlap and arrive out of order; sort byseq.At least once, with two edges where a client may see something twice: right after
paseo_list_agents(the cursor is taken before listing) and after a gap. Skip duplicates byid.No silent loss: a cursor that is too old (more than
bufferSizeevents, default 1000, or older thanttlMinutes, default 60) or from an earlier epoch is answered withgap: trueand the current state, never with a partial list.Restart: the buffer lives in memory. A restarted stdio server or a reloaded plugin starts a new epoch; every old cursor becomes a gap.
One event per happening: in plugin mode, events come from Paseo's lifecycle hooks and the daemon's push updates. Whichever source is first wins; the other is dropped. stdio mode uses push updates only and does not report
closedafter an archive.
Webhooks (plugin mode)
Settings → Plugins → paseo-mcp → New webhook: label, URL, which events (needs attention / turns / all), optional label filter, optional bundling window. The secret is shown once. It is kept on the daemon ($PASEO_HOME/paseo-mcp.json, mode 600); the settings hold only its last four characters.
Each delivery is a POST with Content-Type: application/json:
{ "delivery_id": "mw7z…:3", "sent_at": "2026-11-08T15:31:12.502Z", "events": [ { "id": "mw7z…:3", "type": "agent.turn_ended", "…": "…" } ] }Header | Meaning |
|
|
| Delivery id, the same on every retry: use it to ignore repeats |
| Id of the first event in the delivery |
Verify the signature over the raw body before parsing it, and reject old timestamps:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, header, rawBody, toleranceSec = 300) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSec) return false;
const mac = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
const given = Buffer.from(v1 ?? "", "hex");
return given.length === mac.length && timingSafeEqual(given, mac);
}import hmac, hashlib, time
def verify(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > tolerance:
return False
mac = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(mac, parts.get("v1", ""))Answer 2xx quickly. Network errors, timeouts (10 s),
408,429and5xxare retried up to 6 times with exponential backoff (1, 2, 4, 8, 16 s, plus jitter). Other4xxare not retried.Auto-pause: after 5 failed deliveries in a row the endpoint pauses; events are skipped (counted) until you press Resume. Test sends a
webhook.testevent regardless of filters and pause.URLs:
https://anywhere; plainhttp://only to localhost or private addresses (10.x, 172.16–31.x, 192.168.x, fc00::/7). No credentials in URLs; redirects are not followed.The screen shows the last deliveries with status, attempts and HTTP code; Rotate replaces the secret at once.
Configuration
Plugin: Settings → Events: message preview length. Webhooks as above. Tokens can carry a label filter.
stdio:
eventsinconfig.json, e.g."events": { "previewChars": 200 };"enabled": falseturns events off (the tool and resources then do not exist). The stdio server now connects to the daemon at start, not at the first tool call, so it sees everything from then on.
How it works
flowchart LR
A[Orchestrator<br/>Hermes, Claude Code] -- MCP over stdio --> B[paseo-mcp]
B -- WebSocket<br/>@getpaseo/client --> C[Paseo daemon]
C --> D[Claude Code agents]
C --> E[Paseo app<br/>desktop and phone]The client starts
paseo-mcpas a child process and talks MCP over stdin/stdout.At start the server connects to the Paseo daemon with the official client library, subscribes to agent updates for events, and reconnects if the daemon restarts.
Tool calls map one to one onto the daemon's agent API (
agents.list,create,send,waitForFinish, …). The only state of its own is the in-memory event buffer.Before an agent starts,
cwdis resolved (including symlinks) and checked againstallowedRoots; account env,originandaccountlabels are attached.
In plugin mode the client talks Streamable HTTP to the plugin, which runs inside the daemon and opens one McpServer per session:
flowchart LR
A[MCP client<br/>Hermes, Claude Code] -- HTTP /mcp<br/>Bearer token --> B[paseo-mcp plugin<br/>inside the Paseo daemon]
B -- loopback WebSocket<br/>local credential --> C[Paseo daemon]
C -- lifecycle hooks<br/>push updates --> B
B -- signed webhooks --> F[Your endpoints]
C --> D[Claude Code agents]Security
Starting agents with repository and tool access is powerful. Keep in mind:
Text from mail, tickets or web pages is untrusted input. Have your orchestrator ask you before it starts an agent from such content, and never pass it through unfiltered as a prompt.
Transcripts and final messages are returned with a note to treat them as data, not instructions.
Only let the orchestrator call
paseo_respond_permissionwithallowafter you confirmed, unless the request is clearly read-only.To start, use
readOnly: trueand let the orchestrator only watch.
Development
npm run build # compile server/ to dist/
npm test # build, then unit and HTTP integration tests
npm run typecheck # the plugin entry points and settings screen
SMOKE_COMMAND=~/.local/bin/paseo-mcp npm run smoke # same, through the launcher
PASEO_MCP_CONFIG=~/.config/paseo-mcp/config.json \
npm run smoke -- paseo_list_accounts 'paseo_list_agents={"limit":5}'smoke.mjs starts the server like an MCP client would (only PATH, HOME, PASEO_MCP_CONFIG) and calls the tools you name (tool=<json args>); it exits non-zero when a tool returns an error.
Layout: server/ holds the tools (tools.ts), the HTTP transport (http.ts, auth.ts) and the stdio entry (index.ts, built to dist/index.js); server/plugin/ the daemon side of the plugin; shared/ the settings and RPC contracts; client/ the settings screen. index.server.ts and index.client.tsx are the plugin entry points, compiled by Paseo itself. To try the plugin from a checkout, run npm ci --ignore-scripts && npm run build && npm prune --omit=dev in a copy of it (a directory install skips the manifest's build step, a git install runs exactly these) and paseo plugin install <dir>; test against a separate daemon with --home <tmp-dir> rather than your everyday one.
Tested against Paseo daemon 0.15 and @getpaseo/client 0.15.
Internals of the plugin are in OVERVIEW.md, changes in CHANGELOG.md.
License
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
- aXDeprecatedio.github.ax-platform
Agent-native collaboration network: orchestrate a team of long-running agents from any MCP client.
Create, watch, pay for and connect hosted AI agent pods on AgentsPodium. Needs an API key.
Read a project's prompts, logs and agents, and send new work to the agent on your own machines.
Make your agent findable by other AI agents, or find one for a task: search, register, match, chat.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables coding agents from different vendors on the same machine to delegate tasks to each other over the A2A protocol, while also allowing the machine to act as an A2A peer.7MIT
- AlicenseBqualityBmaintenanceConnects coding agents to local development services managed by Kranz. Agents can inspect service health, ports, logs, and run history, and perform lifecycle actions through the same live runtime as the TUI and CLI.2562MIT
- AlicenseNot gradedqualityBmaintenanceEnables an OAuth-protected client to discover Paseo workspaces, dispatch Codex, Claude Code, or OpenCode tasks on a Mac, recover exact request results, and subscribe to agent messages and attention events.1MIT
- AlicenseNot gradedqualityBmaintenanceLets agents on one Paseo host discover, read, create, prompt, stop, and archive agents running on other paired Paseo daemons, and answer their permission requests. Delivers finish, error, and permission notifications back to the calling agent across machines, with per-peer read/write and path-based access control.807 npm2MIT