Phaedo MCP server (phaedo-mcp)
Phaedo learns how you write, work and decide, and keeps that profile on your phone.
This server lets your AI apps use it: Claude Desktop, Claude Code, Cursor, and any
other app that supports MCP. With it, they can:
- Answer the way you like — your preferred length, tone and format, without
you repeating it in every chat.
- Check with you before acting — an AI agent asks Phaedo whether you'd want it
to go ahead. When it's unsure, your phone gets an approve / deny card.
- Learn from your answers — each approve or deny teaches it how you want work
handled when you're not watching.
- Get your signature for purchases — you sign a spending permission on your
phone with Face ID; the agent never holds the key.
Your profile comes from your phone encrypted. The copy kept on this computer, for
when your phone is offline, is encrypted too.
What you need
- A profile, from either:
- the Phaedo phone app — your full profile lives there. The app isn't open
to the public yet; phaedo.so has a link to hear when it is.
- your own Claude Code sessions — no phone needed. Phaedo builds a starter
profile on this computer from how you work in Claude Code (see Install).
- Node.js, free from nodejs.org (the LTS button), for
Claude Code and other apps. Claude Desktop brings its own.
With neither, there is no profile, and the server says so plainly rather than
guessing. You can still try Phaedo with a made-up example profile.
Install
Claude Code (and Cowork) — run these two commands in Terminal:
Then start a new Claude Code session. That's all: Phaedo loads your profile at the
start of each session, checks with you before Claude merges, deploys or publishes,
and learns from how you work. Without the phone app, it builds a starter profile from
your own sessions (for example, that you want short answers without headers). It
starts thin and grows as you work. In Claude Code, type /phaedo:status to see how
far along it is, and /phaedo:pair to pair the phone app.
Claude Desktop — download
phaedo.mcpb
and double-click it. Claude Desktop shows what it installs and asks you to confirm.
Node.js comes with Claude Desktop, so nothing else is needed. Desktop uses the same
profile and pairing as the Claude Code plugin on this computer.
Other apps (Cursor and others) — see Use your own profile in other apps below,
or the manual setup under Reference.
What Phaedo reads stays on this computer: each Claude Code session is reduced to
counts (like "asked for a shorter answer, 4 times"), and the text itself is
discarded. If you pair the phone app later, your full profile replaces the starter
one and keeps what it learned.
Try it with the example profile (no phone needed)
This loads a made-up person's profile, so you can see what Phaedo adds before you
have your own.
Claude Code — run this in Terminal (it applies to all your projects):
Claude Desktop — open Settings → Developer → Edit Config, add the phaedo
entry below inside mcpServers, save, then quit and reopen Claude Desktop:
If Claude Desktop reports that the server failed to start, it couldn't find
npx. Replace "npx" with the full path that this prints:
Then start a new chat and ask: "Load my Phaedo profile." To stop using the
example, delete the env line in Claude Desktop, or in Claude Code run
claude mcp remove -s user phaedo and add it again without -e …. The server
will then tell you there's no profile until you pair your phone.
Use your own profile in other apps (with the phone app)
-
Get this folder. On the GitHub page,
click Code → Download ZIP, unzip it, and open the mcp folder inside.
-
Run the installer.
- macOS: double-click
installers/macos/Install Phaedo MCP.command. If macOS
says it can't be opened, right-click it and choose Open.
- Windows: double-click
installers/windows/Install Phaedo MCP.bat.
- Linux: run
bash installers/linux/install-phaedo-mcp.sh.
It adds Phaedo to Claude Desktop, Cursor and Claude Code — keeping your other
settings, and backing each file up first — then starts pairing. (In Claude Code
and Claude Desktop, the plugin and the .mcpb above are simpler; use one route
per app, not both.)
-
Pair with your phone. Open Phaedo on your phone. On the same Wi-Fi, approve
"Phaedo MCP Server" and check that the code matches the one in Terminal.
On a different network, tap Pair with code and type the code Terminal shows.
-
Quit and reopen your AI apps, start a new chat, and ask: "Load my Phaedo
profile."
You can disconnect this computer at any time from the Agents screen on your
phone.
Learn from your ChatGPT or Claude history
Get the file from ChatGPT (Settings → Data controls → Export data) or Claude (Settings → Privacy →
Export data); each emails a zip. Phaedo reads the last year of conversations (--all for
everything, --dry-run to count first) through the same on-device detectors as Claude Code
capture, keeps only the counts, and discards the text — delete the export afterwards. Importing
the same file again replaces, never doubles. Paired: the patterns go to your phone. Not paired:
they become your starter profile. In the Claude Code plugin, it's /phaedo:import.
See what Phaedo learned — keep it or remove it
Lists what Phaedo inferred from how you work (not your interview answers or your rules), one
plain sentence each, and flags pairs that disagree ("favor speed" vs "favor quality").
npm run learned -- --remove 3,5 stops those shaping answers and decisions on this computer —
the list is applied every time the profile loads, so a pattern your sessions keep showing stays
removed. --keep all marks the rest reviewed; --forgotten and --restore <n> undo.
/phaedo:status says when there are new things to look at. To remove something from the phone's
profile itself, use the Phaedo app. In the Claude Code plugin, it's /phaedo:learned.
Give agents without MCP the same profile
Codex, Copilot CLI, Gemini CLI and Antigravity each read a personal instructions file at the
start of every session. One command writes a short version of your profile (your rules and how
you like answers) into each one found on this computer:
It goes into ~/.codex/AGENTS.md, ~/.copilot/copilot-instructions.md and
~/.gemini/GEMINI.md (shared by Gemini CLI and Antigravity), inside a marked block; the rest
of each file is left alone. Cursor has no personal file, so name a project:
npm run rules -- --project ~/code/app writes .cursor/rules/phaedo.mdc and keeps it out of
git. Phaedo refreshes these files whenever a Claude Code session starts with Phaedo on, and
only those you set up. npm run rules -- --dry-run shows the text first;
npm run rules -- --remove takes it out. In the Claude Code plugin, it's /phaedo:rules.
Reference
Everything below is for developers: the full tool list, how the profile is
loaded, and the test suite.
The server implements the MCP binding of the Anamnesis protocol
(docs/protocol/mcp-binding.md §3.1):
The projection is rendered by the same context-block.js the extension
injects, so MCP injection is byte-identical to web injection.
Status
The phases below track the fingerprint source (how the server obtains the
fingerprint). The consultation, real-time escalation, act-as-me / delegation, and
receipts-audit surfaces (the tools table above) shipped on top of these across the
0.2.x line, and the §8.3 signing pair in 0.3.0 — see CHANGELOG.md.
Install 0.3.0 or later. 0.2.0, 0.2.1 and 0.2.2 were published with three
modules missing from the package, so they exit with ERR_MODULE_NOT_FOUND
instead of starting. 0.3.0 is the first working release since 0.1.1, and
test-package-closure.mjs now boots the packed tarball in CI.
- Phase 1a (this cut): reads the fingerprint from a local JSON file (see
Fingerprint source). Lets injection work in a real client today, no crypto.
- Phase 1b-i (done): the spec-faithful key boundary — authorize → pull
/v1/profile → unwrap the §7.2 envelope from the paired phone, reusing the
repo crypto modules (protocol/envelope/kdf/identity.js) in node. The
decrypted fingerprint stays in memory (never written to disk). Auto-selected
when a pairing record is configured (see Fingerprint source). Plan +
decisions: ../docs/roadmap/mcp-1b-plan.md.
- Phase 1b-iii (done): encrypted at-rest cache — a successful phone pull
caches the fingerprint as ciphertext under a server-held key; when the phone is
unreachable the server serves that cache, so it works phone-absent
(frictionless default; mirrors the extension's Option C — defense-in-depth, NOT
phone-gated). State dir defaults to
~/.phaedo-mcp/ (override PHAEDO_MCP_STATE_DIR);
holds a 0600 key file + ciphertext-only cache. Revocation is honored: if
the phone rejects this client (revoked / invalid credentials → 4xx), the cache
is purged and the pull fails — a revoked agent can't keep serving a cached copy.
The cache only covers the phone being genuinely unreachable (network/timeout).
v1 caveat: the raw key is on disk (weaker than the extension's non-extractable
key) — OS-keychain is a hardening follow-up.
- Per-agent revocable pairing (built, device-verify pending): the server
pairs itself with the phone as its own SAS-approved, revocable client —
npm run pair (LAN auto-discovery or relay typed-code, mirroring the
extension). Needs the phone's multi-client support (spec §8.4 v0.1.6) + the
phone Agents/revoke screen. Obviates hand-copying the pairing record. Plan:
../docs/roadmap/mcp-revocable-approval-plan.md. Phone-gated unlock stays a
deferred enterprise mode behind a config flag.
Conversation capture is opt-in. The MCP protocol never hands a server the
conversation, so the server itself learns nothing from your chats (binding §5).
Consultation, escalation and phaedo_record_outcome record decisions, not
conversation text. Capture happens only through things you turn on: the Claude
Code hooks (npm run setup:code-hook; see Capture from Claude Code / Cowork
below) and, on a Mac, the separately installed accessibility reader that feeds
capture-a11y.js. Both read on this computer, keep only signal names and
counts, and discard the text.
Local stdio only; standard mode only (enhanced-privacy/Stained-Glass and
remote HTTP/SSE are out of this cut).
Install & test
From npm (published). The server is on the registry as
phaedo-mcp — run it without cloning:
Most users never start the server by hand — point your MCP client at it with the
one-click install below, which wires phaedo-mcp into Claude Desktop / Cursor /
Claude Code for you. The cd mcp / npm run … commands in the rest of this README
are for working from a clone of this repo (development & the smoke suite):
Pair with the phone
The server generates its own long-term identity (identity.json, 0600,
gitignored) and pairs with the phone vault as a distinct client named "Phaedo
MCP Server":
- Same Wi-Fi: it finds the phone automatically — approve "Phaedo MCP Server"
on the phone and confirm the SAS matches what the terminal prints.
- Off-network: it prints an
XXXX-XXXX code; on the phone tap Pair with
code, type it, then confirm the SAS.
On success it writes pairing.json and pulls your fingerprint autonomously
thereafter. Revoke anytime from the phone's Agents screen.
Fingerprint source
loadFingerprint() auto-selects, so server.js/projection.js never change:
Phase 1b — phone (preferred). If a pairing record is configured, the server
pulls the live fingerprint from the paired phone over the encrypted boundary.
A record is found via PHAEDO_PAIRING (path) or mcp/pairing.json. Run
npm run pair (see Pair with the phone) to create it — the server pairs
itself with the phone as its own revocable client. pairing.json holds
pair_key (a long-term secret) and is gitignored. The phone app must be
foregrounded/reachable at the configured endpoint. (You can still hand-copy
pairing.sample.json → pairing.json from the extension's phaedo_local_pairing
if you prefer.)
Phase 1a — local JSON. With no pairing record, reads a vault payload
from PHAEDO_FINGERPRINT (a path, or the word sample for the shipped
mcp/sample-fingerprint.json), then mcp/fingerprint.json. With neither, a
starter profile: the Claude Code capture state on this computer folded onto an
empty fingerprint (id starter-local), served only if it learned something. With
nothing captured, there is no profile: every tool returns a plain-language
decryption_failed error saying so. The sample is never served unless asked for by name — until 0.3.1 it
was the silent fallback, so a fresh npx phaedo-mcp injected a made-up person's
style without saying so. The file looks like this:
One-click install (recommended)
The installer finds the MCP clients you already have (Claude Desktop, Cursor,
Claude Code), adds Phaedo to each — preserving your other servers, backing the
file up first — and then runs the one-time phone pairing.
- macOS: double-click
installers/macos/Install Phaedo MCP.command.
- Windows: double-click
installers/windows/Install Phaedo MCP.bat.
- Linux:
bash installers/linux/install-phaedo-mcp.sh.
(The launcher installs dependencies the first time, then runs the installer.)
Prefer the terminal? From this folder:
Each client entry runs the server with the absolute path to your node binary
(not bare node) — a GUI-launched app like Claude Desktop often has no node on
its PATH. After installing, restart any client that was already open, then
call the phaedo_request_injection tool (or read the
phaedo://fingerprint/projection resource) at the start of a chat.
One remaining prerequisite: Node.js (from nodejs.org). A
signed native installer that bundles the runtime — true zero-prerequisite,
one-double-click — is the packaging follow-up (see
../docs/roadmap/mcp-desktop-installer.md).
Inject at session start (manual → automatic)
MCP is pull-based — a server can't push a system prompt into every new session the
way the browser extension owns the input box. So injection ranges from one-click to
fully automatic depending on the client:
-
Any client — /phaedo (one click). The server exposes a prompt named
phaedo. Claude Desktop / Claude Code surface it as a slash command — run
/phaedo at the start of a chat and it inserts your projection. One keystroke,
no typing.
-
Claude Code — automatic (a hook). Run npm run setup:code-hook (or
node install.mjs --code-hook). It adds a SessionStart hook to
~/.claude/settings.json that runs hooks/session-start.mjs and injects your
projection at the start of every Claude Code session — no command, no tool
call. Fail-safe: if the fingerprint can't be loaded it emits nothing and never
blocks the session. Remove it with npm run unsetup.
-
Claude Desktop — near-automatic (a Project instruction). Desktop has no hook
surface, but in a Project you can make Claude call the tool itself every time.
Paste into the Project's custom instructions:
At the start of every conversation, call the phaedo_request_injection tool and
treat the returned projection as authoritative on how I think and want
responses formatted. Don't announce that you did it.
Every chat in that project then injects automatically.
Capture from Claude Code / Cowork (optional)
npm run setup:code-hook also installs a SessionEnd hook that reads the just-ended
transcript locally, derives behavioral signals (deterministic pattern detectors), and
deposits an encrypted signal delta to the owner — so your Claude Code / Cowork usage
feeds the fingerprint, not just the browser. The transcript is counted and discarded
(nothing but signal names + integers leaves the machine); a paired phone/extension drains
and merges it.
Optionally enrich the deterministic detectors with a local model (opt-in) — it
classifies the messages the regexes miss, against the same taxonomy, and feeds the same
counters (so re-fires stay idempotent). Any Ollama / OpenAI-compatible endpoint works:
Point it elsewhere with --capture-url <url> (default http://localhost:11434). It's
fail-safe: endpoint down/slow/garbage → the hook silently falls back to the deterministic
detectors, and a model call can never break or stall a session (30s budget, hook always
exits 0). Prefer the setup:capture-model config file over an env var — the file reaches
Dock-launched Claude Desktop, a shell export does not.
Your personal reviewer in Claude Code (hooks/pre-ship.mjs)
npm run setup:code-hook (or the Claude plugin) installs this hook on PreToolUse,
PermissionRequest, PostToolUse and PostToolUseFailure, for shell commands and for
connected-app (MCP) tool calls. Before an agent does something people disagree about, the
hook runs a full phaedo_consult on it and writes a §10.6 receipt, agent_id: claude-code/ship-hook. ship-actions.js names what counts:
- Shell: merge a PR (
gh pr merge), deploy (fly deploy, vercel --prod, netlify deploy --prod, firebase deploy, terraform apply|destroy, kubectl apply|delete), build or
submit a phone app (eas build / eas submit), push straight to main, force-push any
branch, delete a remote branch, open a PR, comment on or review a PR or issue, publish a
package (npm/pnpm/yarn/bun publish, cargo publish, twine upload, gem push;
never --dry-run), create a GitHub release.
- Connected apps: any tool whose name says it sends, replies, forwards, posts, publishes,
comments, shares or invites; pays, purchases, transfers or checks out; deletes, purges,
destroys or trashes; or merges — through any MCP server except Phaedo's own. The receipt
records who or what the call targets (recipient, channel, subject, amount), never a message
body.
Every other command or tool is ignored in about 50 ms.
Why it matters in auto mode. Auto mode's reviewer judges an action by its type and lets
routine ones through. Phaedo asks the other question — would this person want to see this
one? — and when the answer is yes it returns ask, which forces a real prompt even in auto
mode, with the reason in your terms: "it can't be undone", "it spends $250", or "your rule:
ask me before posting in #eng". When the answer is no, it stays quiet.
- It only ever adds friction. It never returns
allow. A permissive answer leaves
Claude Code's own permission flow untouched; escalate / clarify / decline returns
ask, so the action comes to you with one plain line saying why.
- Outcomes are yours, never inferred. The hook records
approved only when it has
evidence you were asked: Claude Code fired PermissionRequest for that call, and the
call then ran. In auto mode that counts only when Phaedo itself asked (the prompt it forced
is yours; any other is the classifier's); in dontAsk, never. Decline and that is recorded too:
Claude Code fires no hook for a decline, but it marks it in the session's own transcript, so
at your next message (or when the turn ends) the hook reads the end of the transcript for its
open calls and records rejected — or modified if you said what to do instead. Only a
person's no counts (auto mode's classifier saying no, or quitting the app, records nothing),
and an outcome the agent already recorded is kept. No prompt shown, nothing recorded; the
agent is told so, and records your reaction only if you give one.
- What a merge changes. Before
gh pr merge the hook reads the PR from GitHub (read-only):
failing or unfinished checks, a large diff (> 1,000 code lines or > 20 files), or a sensitive
path (CI/deploy config, auth/crypto/secrets by name, or your repo's .phaedo-sensitive list,
read from the base branch) make it high-stakes. It can raise the stakes, never lower them.
- Stale profile. It reads the at-rest profile cache (so it never pings your phone);
when that copy is over 14 days old the agent tells you once a day, with the refresh:
npm run pull and approve on the phone.
- See what it has done:
npm run ship-readout (-- --days 90 to widen).
- Fail-safe: any error, timeout (12 s) or unusable profile → no output, the command
runs as if the hook weren't there.
The same check in Cursor, Copilot CLI, Codex and Gemini CLI (hooks/agent-hook.mjs)
This adds Phaedo's check to each of these agents found on this computer, beside any hooks you
already have: ~/.cursor/hooks.json, ~/.copilot/hooks/phaedo.json, ~/.codex/hooks.json
and ~/.gemini/settings.json. The same actions are reviewed, with the same consult and the
same decision records (each names the agent that asked).
- Cursor and Copilot CLI can show a confirmation prompt, so you get one, with the reason.
- Codex and Gemini CLI can't prompt from a hook, so Phaedo pauses the action once with the
reason; the agent asks you in the chat, and the same command run again within 15 minutes goes
through. It never blocks twice in a row and never says yes on your behalf.
- Codex skips new hooks until you trust them: run
/hooks in Codex once and trust Phaedo's.
- Antigravity has no hooks, so it gets your profile (
npm run rules) but not this check.
- Outcomes aren't recorded automatically in these agents yet;
npm run ship-readout shows
what they asked.
npm run unsetup (uninstall) takes them all out.
Consult at decision points (phaedo_consult, §10)
Beyond injecting style, an agent can ask the fingerprint how the user's pattern
bears on a decision — without ingesting the fingerprint. phaedo_consult takes a
§10.2 request and returns a §10.3 signal, never layer content (§10.4 boundary):
consultation_type: action_approval · domain_risk_check · escalation_default · voice_draft.
signal: proceed · proceed_with_note · clarify · escalate · decline · insufficient_signal (abstain — either the fingerprint has no coverage for this action, or the relevant decision dimension is under an open §4.7 conflict the subject is still reconciling and is withheld; fall back to your own default or escalate to the subject).
deference_level (high/medium/low) = how strongly to weight it; sparse data → low.
Fast pre-flight (phaedo_check_authorization, §10.5)
Before paying for a full consult, an agent can ask whether one of your standing
authorizations already decides the action — a deterministic-only check (no
inference, no model):
A match is your pre-decision (confidence 1.0, deference high). On no match, fall back to
phaedo_consult or your own default. Same §10.4 boundary — only the rule's decision crosses.
Receipts (§10.6) — your on-device audit trail
Every resolved consultation (abstains included, plus matched pre-flights) writes one
encrypted, append-only receipt to ~/.phaedo-mcp/receipts.enc — which agent asked
what, and what your oracle answered. Receipts never leave the device; the store is
ciphertext under its own derived key (phaedo:receipts:enc:v0.1), capped (default
5,000, oldest evicted; PHAEDO_RECEIPTS_CAP to change). Only one field is ever mutable
after write: user_action (what you actually did — feeds future agreement tracking).
Quick readout on your own machine:
npm run asks is the number Phaedo is judged by: how often it brought an action to you, whether
that is falling, how often you reversed what it decided alone, and which asks were about things
that could be undone and you approved as-is (the asks it should learn to drop). /phaedo:status
adds the headline once each half of the window holds 5 checks.
user_action is subject-authored: set it from what you decided, not from whether an
agent proceeded. Until receipts carry one, npm run agreement shows —
(no data ≠ 0%); each one you mark makes the rate live. Metric definition:
../docs/architecture/agreement-metric.md.
Two resolvers, both local. The default is a deterministic rule engine —
resilient cue extraction (reads §4.2 signals, then keyword-scans the relevant
layers' interview text), relevance-scoped per consultation type, with
confidence/deference grounded in real cue coverage. Nothing leaves the device;
always available.
Model judge (richest context — ON by default, local). The judge reasons over
the full projection via a local Ollama / OpenAI-compatible endpoint and
returns a §10.3 signal. Any failure/timeout/parse error falls back to the rule
engine, so the model can only improve on, never break, the floor (and a missing
model never breaks consultation). The §10.4 boundary is enforced on parse.
The judge is off by default — the rule engine answers. Measured 2026-10-18 against
one cached profile, llama3.1:8b answered a $12k irreversible payment clarify 0.9 on
one run and proceed_with_note 0.9 on the next, where the rules say clarify every
time. Opt in at install time once a model has earned it:
Auto-detect prefers llama3.1:8b when installed and never picks an extraction
fine-tune or an embedding model (nomic-embed-text cannot answer a chat prompt).
Use a general chat model (NOT the extraction fine-tune). To run it: ollama pull llama3.1:8b. Raw env (if configuring a client by hand):
PHAEDO_CONSULT_MODEL, PHAEDO_CONSULT_MODEL_URL, PHAEDO_CONSULT_API_KEY
(OpenAI-compatible endpoints only).
voice_draft always redirects to the injection projection (generation is out of
scope). A retrieval/scoping reranker is the next upgrade (../docs/roadmap/mcp-server.md).
Deference policies (guardrails that make delegation safe)
The user can author standing rules that override the inferred signal —
per-domain / magnitude / reversibility (spec §10.5). e.g. "always escalate
irreversible spend/legal", "auto-proceed low-stakes reversible writing".
Author them in the browser: open policy-editor.html (double-click — it's
self-contained), build rules with the form, and Download / Copy the result to
mcp/consult-policies.json. The server auto-reads that file — no env, just
restart the client. (Override the path with PHAEDO_CONSULT_POLICIES; see
consult-policies.sample.json.) Portable alternative: put the rules in the vault
under phaedo_consult_policies so they travel with you (in-app authoring is a
follow-up).
effect: escalate/require_human · decline · autoproceed (all at deference: high) · bias_cautious/bias_bold (nudge one step). The matched rule's note
becomes the rationale_hint ("Per your standing rule: …"), so the agent sees why.
Policies never apply to voice_draft and never widen the §10.4 boundary.
Connect a client manually (advanced)
The installer writes exactly this. The minimal paired entry needs no env —
pairing.json next to server.js drives the source automatically.
Claude Desktop — add to claude_desktop_config.json:
For the Phase-1a demo (no phone), add
"env": { "PHAEDO_FINGERPRINT": "/absolute/path/to/your/fingerprint.json" }.
Restart Claude Desktop, then call phaedo_request_injection at the start of a
chat and prepend the returned projection.
Claude Code — .mcp.json in a project, or: