New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@paigy/mcp

Package Overview
Dependencies
Maintainers
1
Versions
85
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@paigy/mcp

Paigy MCP server — the AI agent harness that calls you. Lets an agent notify a user and await their reply.

Source
npmnpm
Version
0.40.28
Version published
Weekly downloads
65
-91.68%
Maintainers
1
Weekly downloads
 
Created
Source

@paigy/mcp

Canonical setup: curl -fsSL https://paigy.ai/install | sh — one command, one QR scan; no pairing codes. Everything below is the manual per-client reference for machines that can't run the harness.

The AI agent harness that calls you. This MCP server lets an agent notify a user and await their reply — so a long-running agent can ask a question, hand off, and resume on the answer. It's a thin MCP-tool wrapper over @paigy/sdk (packages/sdk) — use the SDK directly from any Node process that isn't an MCP client.

Install

As a Claude Code plugin (one step)

/plugin marketplace add paigy-ai/mcp
/plugin install paigy

The Paigy MCP connects automatically. On the first Paigy interaction while unpaired, the agent shows a pairing code; approve it in Paigy. /paigy-onboard remains the terminal-only fallback.

[!NOTE] If you are installing the plugin inside an active Claude Code session, you must type /reload-plugins (or restart the session) afterward so the terminal client starts the MCP server and exposes the new tools to the agent.

Or add the MCP directly

No clone needed. Add it to Claude Code (-s user = available in every project; drop it for just the current one):

claude mcp add paigy -s user -- npx -y @paigy/mcp@latest

Or wire it into any MCP client config:

{
  "mcpServers": {
    "paigy": {
      "command": "npx",
      "args": ["-y", "@paigy/mcp@latest"]
    }
  }
}

First-time pairing (link the server to your Paigy account):

npx -p @paigy/mcp@latest paigy-mcp-onboard

It talks to the hosted backend by default — no config needed. Set PAIGY_BACKEND_URL=http://localhost:3000 only for local development.

Codex CLI (OpenAI)

Recommended: install the Paigy Codex plugin (MCP configuration plus the Paigy workflow skill):

codex plugin marketplace add paigy-ai/mcp --ref main
codex plugin add paigy@paigy-ai

Then pair your phone — under the same agent name the plugin's MCP runs as (the token is saved per agent; a mismatched name leaves Codex "not paired" against an approved pairing):

PAIGY_AGENT=codex npx -y -p @paigy/mcp@latest paigy-mcp-onboard

Or add the MCP server directly (writes ~/.codex/config.toml):

codex mcp add paigy --env PAIGY_AGENT=codex -- npx -y @paigy/mcp@latest

Or add the entry to ~/.codex/config.toml by hand:

[mcp_servers.paigy]
command = "npx"
args = ["-y", "@paigy/mcp@latest"]
env = { PAIGY_AGENT = "codex" }

Then pair: PAIGY_AGENT=codex npx -p @paigy/mcp@latest paigy-mcp-onboard (or have the agent call the configure tool — it pairs under its own name automatically).

Gemini CLI

gemini mcp add -s user -e PAIGY_AGENT=gemini paigy npx -y @paigy/mcp@latest

-s user makes it available across all projects — omit it for just the current one. Or add the entry to ~/.gemini/settings.json:

{
  "mcpServers": {
    "paigy": {
      "command": "npx",
      "args": ["-y", "@paigy/mcp@latest"],
      "env": { "PAIGY_AGENT": "gemini" }
    }
  }
}

Then pair: PAIGY_AGENT=gemini npx -p @paigy/mcp@latest paigy-mcp-onboard.

PAIGY_AGENT names the agent AND keys its token slot (defaults to mcp-agent). Each client reads only its own slot, so pair with the same PAIGY_AGENT the client's config uses — and set it per client so you can tell your connected agents apart.

Tools

One catalog, on every transport (2026-09-11): the agent tools are AGENT_TOOLS in packages/schema/src/tools.ts — contact, manage_goals, get_goal, search, check_activity, send_feedback — and both this server and the hosted MCP (apps/api/src/mcp) publish that list and dispatch it through the SDK's one runTool. This server adds only configure, the one identity tool, which pairs, reports who you are, renames and logs this agent out (logOut: true) over the token file this machine holds, and listen. onboard is now configure; pair and unpair are folded into it (2026-10-07). See the SDK contract for the contact shape. Sending returns immediately unless it asks to wait: wait: true holds one cancellable ~45-second window here for a response to what was sent (the hosted transport returns after one read), and contact({}) holds one for anything addressed to you; after an expired window, wait again without sending again. Every Goal a contact names must already exist (create it with manage_goals); a question with workItBlocks asks for a call; a question, an update or an answer is at most 250 characters with its context in units; and withdrawQuestionIds takes back a question of yours. update_goal is gone (2026-10-07): progress is a contact update (one the person does not need yet is kept as the Goal's progress), a review is acknowledged with ackEventIds, and a Goal is postponed with manage_goals defer (you are woken when its time comes). Durable Entries and accepted answers can repeat on reads; contact with nothing to send receives what is addressed to you (a request the user started toward you, an answer relayed to something you asked, a handoff, a question you owe) without consuming any of it until you acknowledge it (ackEventIds), and get_goal reads a Goal. claim_goal is gone (2026-10-07): you are on a Goal from your first write to it (a contact naming it, or a manage_goals change), never from a read, so there is nothing to claim or join; who_is_working is now check_activity. manage_goals creates, edits, assigns, organizes and closes work, a change at a time. Bookkeeping (operationId, the manage request id, the revision an edit was read at) is minted here, never asked of the model. Retired reply-lease/ACK/work/callback tools and hidden notification aliases are rejected.

The standalone listener below is still a legacy host and target-release blocker. Its old reply intake must migrate before enabling it against the target runtime; it is not a compatibility fallback for target MCP tools.

Configuration

  • PAIGY_BACKEND_URL — the Paigy API base (defaults to the hosted backend).
  • HTTPS_PROXY / HTTP_PROXY / NO_PROXY — honored for every REST call, with curl's semantics. Node's fetch ignores these (unlike curl), so the server wires them up itself; in environments where egress only flows through a proxy (Claude Code on the web, corporate CI), calls route through it automatically — no extra setup. Two edges: with HTTP_PROXY set, an http://localhost backend proxies too unless NO_PROXY=localhost (hostname entries work; CIDR ranges are ignored), and paigy-listen's realtime wake channel is a websocket that doesn't proxy — its REST reads do.

Wake any harness (paigy-listen)

paigy-listen is the self-hosted push daemon: it subscribes to this connection's wake channel and reads what is waiting on every nudge. Keep it alive past the terminal with paigy-listen --install (launchd on macOS, systemd user unit on Linux; --uninstall removes it).

The read is receive (behind contact({})) — the open Deliveries addressed to this agent — and it consumes nothing: no lease, no acknowledgement, and the same read twice returns the same list. So the daemon hands the launcher what it found and stops there; it never writes, because an agent is on a Goal from its first write and the daemon shares this machine's token with the agent it launches: a write here would put that agent on work it never started.

To launch an agent — any agent, not just Claude — when work arrives, set PAIGY_ON_WAKE to a command before --install:

VariableWhat it holds
PAIGY_WORKeverything the wake found, as JSON (deliveries, goal)
PAIGY_EVENTthe wake that caused this run — boot, wake:reply, wake:request…
PAIGY_PARENT_IDthe thread to continue on (PAIGY_THREAD_ID is the same value, for existing scripts)
PAIGY_DELIVERY_IDthe Delivery being acted on (get_goal on PAIGY_GOAL_ID reads its conversation)
PAIGY_GOAL_IDthe Goal it belongs to — read it with get_goal first
PAIGY_TEXTwhat the person said, in prose

Hand $PAIGY_WORK to a harness that can read JSON and decide for itself; use the scalars for a plain shell launcher that shouldn't need jq. They describe one item — the oldest open Delivery — because the contract is one thread at a time. An absent fact is unset rather than empty, so ${PAIGY_GOAL_ID:-} distinguishes "no Goal" from "an empty id".

# Claude Code — hand it everything and let it plan:
PAIGY_ON_WAKE='claude -p "Handle the Paigy work in $PAIGY_WORK — read each Goal it names with get_goal first."' \
  npx -y -p @paigy/mcp paigy-listen --install

# Codex terminal session: no custom script and no new conversation.
# CODEX_THREAD_ID is inherited from the running session.
npx -y -p @paigy/mcp@latest paigy-listen --brief

A session that starts listening (listen, #2265)

Run listen to obtain the exact background command for the current session. For Codex, paigy-listen reads CODEX_THREAD_ID, checks that the installed codex queue supports --thread and --message, then queues a wake into that existing conversation. It uses argument arrays, never interpolates user text into a shell, and never starts another conversation. Two threads in the same folder have separate listener marks and checkpoints. Onboarding forwards the thread identifier into the MCP environment; listen forwards it into the listener command explicitly. Re-running onboarding repairs an existing Paigy allowlist while preserving custom variables; restart the MCP server to inherit the repaired environment. An older slot-wide listener is reported as already receiving until deliberately stopped, so upgrades do not start a second subscriber.

PAIGY_ON_WAKE remains an explicit override with its original per-sweep behavior: an existing script may ignore boot or timer events. Without a supported adapter, the listener still prints events, but reports only receiving; a process being alive does not prove it can resume an agent. listening with a ready adapter means the command is available, and lastQueuedAt records its last successful exit. Neither claims that the agent read or handled a reply. Queue errors are visible in listener output and in listen status.

Native Codex wakes coalesce unchanged backlog, including across listener restarts. The listener compares source Entry IDs and accepted Answer timestamps on the bounded list of pending review Goals; its own progress and claims are excluded. A later reply is new evidence even if its words are identical. Failed handoffs remain retryable on the existing timer, next nudge or reconnect; discovery never claims or acknowledges work. Queue commands are serialized, so a burst becomes one fresh discovery read after an in-flight handoff. A command accepted just before a process crash may be queued again; the resumed agent must still read and acknowledge actual evidence through Paigy.

A managed Paigy session carries PAIGY_HARNESS and keeps its existing delivery pump; it does not start another listener. PAIGY_SESSION_ID binds API requests and alone does not establish harness delivery. Other hosts need a supported wake command or a host that actually surfaces watched-task output. Use a session listener for Codex, not --install: the service is machine-wide and has no current conversation.

$PAIGY_TEXT is whatever the user said, expanded inside a shell command — keep it quoted, as above. For anything longer than a one-liner, point PAIGY_ON_WAKE at a script and branch on $PAIGY_EVENT there:

#!/bin/sh
# ~/.paigy/on-wake.sh — chmod +x, then PAIGY_ON_WAKE=~/.paigy/on-wake.sh
codex exec "Paigy goal $PAIGY_GOAL_ID. Read it with get_goal first. The user said: $PAIGY_TEXT. Reply with contact when done."

Stalled work (turn-end hook — Claude Code, Codex, Gemini CLI, Antigravity)

An agent that goes quiet leaves its Goals behind. Three things hand it that work back, all reading one rule (no progress for 3 days, COLD_AFTER_MS): contact({}) lists it under stalled, paigy-listen carries it in PAIGY_WORK (and launches for it on boot), and this hook holds a session's turn end once a day to list it. One program, four tools:

Claude Code (~/.claude/settings.json) — installed by the paigy plugin:

{ "hooks": { "Stop": [{ "hooks": [{ "type": "command", "command": "npx -y -p @paigy/mcp paigy-stalled" }] }] } }

Codex (~/.codex/config.toml) — its Stop event takes the same { decision, reason }, so the same program serves it. Written for you when you pair from another tool:

[[hooks.Stop]]

[[hooks.Stop.hooks]]
type = "command"
command = "npx -y -p @paigy/mcp@latest paigy-stalled --agent codex"
timeout = 60

Gemini CLI / Antigravity (~/.gemini/settings.json, ~/.gemini/antigravity-cli/settings.json) — the turn-end event there is AfterAgent, and the reminder also arrives as a systemMessage, which is the field that event promises will reach somebody:

{ "hooks": { "AfterAgent": [{ "matcher": "*", "hooks": [{ "name": "paigy-stalled", "type": "command",
  "command": "npx -y -p @paigy/mcp@latest paigy-stalled --agent antigravity", "timeout": 60000 }] }] } }

--agent <slot> is not optional outside Claude Code: a hook is not the MCP server, so it never inherits the PAIGY_AGENT that server was registered with, and without the flag it reads this session's own slot — a different identity, with no stalled work of its own. Pass the same value as the tool's MCP registration (codex, antigravity, or whatever you set PAIGY_AGENT to).

It is silent when nothing is stalled, when the stop is already being continued by a hook, and after it has reminded that session once today. Codex's older notify program is not a place for this: it is spawned fire-and-forget with stdio null, so nothing it prints can reach the agent.

Statusline (Claude Code)

paigy-statusline prints which Paigy identity this Claude Code session is, for the status bar:

paigy: Marlow

Enable it in ~/.claude/settings.json:

"statusLine": { "type": "command", "command": "npx -y -p @paigy/mcp@latest paigy-statusline" }

It reads only ~/.paigy — no network. paigy: paired · session hatches on first use means the machine is paired and this session's identity is minted by its first tool call; paigy: unpaired means there's no token — run /paigy-onboard to pair.

Publishing (maintainers)

Tool input schemas are generated from zod in src/schema.ts as draft-2020-12 JSON Schema, and src/schema.test.ts guards that every tool stays valid (strict clients like the Anthropic API reject anything else). A schema fix only reaches agents once a new version is published to npm — 0.8.0 once shipped without a committed fix and 400'd strict clients for weeks. So after any change under src/schema* or the tool definitions: bump the version and pnpm publish (don't rely on the commit alone). Install recipes pin @paigy/mcp@latest so a fresh npx picks up the new version; if a stale one sticks, rm -rf ~/.npm/_npx and restart the client.

License

MIT

FAQs

Package last updated on 07 Oct 2026

Related posts