pi-femboy-mcp
Integrates with Git events via hooks to report commits, pushes, force-pushes, and rebases, feeding the Git Master achievement chain and progress tracking.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@pi-femboy-mcp give her a headpat intensity 5 and remember I like boba tea"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
♡ pi-femboy-mcp — pastel state, mood & memory server
MCP server that holds the shared state of the pi-femboy-tui ecosystem: the current
outfit, the emotional vector, unlocked wardrobe, short-term memory about the user, and
which terminal window the chibi is currently living in.
Status: Phases 1-4 + 6 ♡ done. Transports, persistence, reactive subscriptions, the full
Phase 2 resource/tool surface, the Phase 3 tamagotchi engine (background decay, biorhythms,
terminal-exit-code hook) and the Phase 4 loot loop: achievement detectors, the status-line
toast rail and a closet where the rare skins cost something. Phase 6 has landed: the
pi-femboy-tui extension now pairs over streamable HTTP — shared outfit, headpat counter and
feature flags across all windows, gated skins enforced by the server, graceful local-only
fallback. Presence multi-window focus/jump-around (Phase 5) and external status sharing are
still on the roadmap (femboy_mcp_roadmap.md).
Phase 1 ♡ done core + dual transport + sqlite store + resources/subscribe broadcast
Phase 2 ♡ done 5 resources · 10 tools · 2 prompts · mood mechanics
Phase 3 ♡ done background ticker · circadian decay · terminal-report hook · comfort mode
Phase 4 ♡ done 6 achievements · detectors · toasts · gated wardrobe · femboy://progress
Phase 6 .. ♡ done pi-femboy-tui pairing: shared vibe sync · gated skins · offline fallback
Phase 5 · 7 .. presence focus/jump-around, external status sharing, in-TUI soak testsWhat is exposed today
Resources (all application/json, all reactive)
URI | Payload | Notes |
| outfit, accessory, status text, activity, badge, feature flags, locale, unlocked skins/achievements/accessories, item counts, |
|
|
|
|
| every connected terminal, transport, pid, tmux pane, heartbeat, focus, |
|
| the current line ( |
|
| newest-first monologue entries with |
|
| every achievement with |
|
Every snapshot carries schema, revision, updatedAtMs, updatedBy and changedKeys, so a
client can drop a stale notification and tell what actually moved. Diffs are sorted and
deduped in the engine, so changedKeys is comparable without normalising.
Tools
The seven the roadmap specifies:
Tool | What it does |
| Wear an owned skin; accepts |
| Flip a pastel feature flag ( |
| 1-10 (default 3). +affection, +patience, −a little energy, saturating near the top. She reacts on the speech buffer; |
|
|
| Writes to |
| Rubber-duck log. Always answers with one socratic question, never a fix. Re-call with |
| One line of inner monologue, stamped with the mood that was live at write time. |
The three Phase 4 loot tools:
Tool | What it does |
| Read the rail: progress per achievement, the closet with its prices, pending toasts, the counters behind them. |
| Take a popup off the rail after the status line displayed it — a real mutation on |
| The git hook: |
Three extras, because the Phase 2 resources would otherwise be unreachable or write-only:
Tool | Why it exists |
| the only writer for |
| the only reader for what |
| replays the diff journal for a client that was offline |
…plus the Phase 3 environment hook:
Tool | What it does |
| The terminal-status API hook, and the main Phase 4 event source. Failures raise the streak and bite patience (compounding, capped); 3+ in a row switches her into sympathy mode — a comfort line on the bubble, one diary note, |
Prompts
companion_persona(focus?, verbosity?, include_diary?)— assembles the live server state into a system prompt: outfit and feature flags, mood vector with its label profile, her last spoken line (so she does not repeat it), remembered facts with confidence percentages, recent diary lines, connected terminals and which one is focused, headpat and open-confession counters — plus the behavioural rules. If the stored label disagrees with the numbers, the prompt says so and tells the model to trust the numbers.rubber_duck(problem, attempted?, language?)— socratic mode: exactly one question per turn, never a proposed fix, tone adapted to current patience, and it lists the open bug confessions already on the server so a duplicate is not started.
Mood mechanics live in one place
src/mood/labels.ts (deriveMoodLabel, moodProfile), src/mood/effects.ts (headpat
curve, item catalog, confession/relief deltas, command-report arithmetic, duck nudge wheel)
and src/mood/rhythm.ts (biorhythm decay) are pure functions. Every tool and the ticker
goes through them — a decay tick and a headpat can never disagree about what blushing
means. Labels are ordered so trouble (sleepy / worried / lonely) wins over pretty
(purring / blushing / smug).
The tamagotchi engine (Phase 3)
A background MoodTicker (src/state/ticker.ts) runs on tickIntervalMs (default
60 s, unref'd, tick.enabled = false to silence). Each tick advances the vector by real
elapsed time since the stored lastTickAtMs through simulateTick (src/mood/rhythm.ts):
Energy — −0.4/min while a terminal heartbeat or a tool interaction says the user is on shift; at night (23:00-05:59) sleep restores +0.9/min — unless she is staying up with you, which costs her; 06:00-08:59 morning lift pulls toward 60 (утренняя бодрость); idle hours drift back to a 55 resting baseline. Items remain the only instant boosts.
Affection — never self-heats. Cools after 3 h without care, double speed after 8 h (the road to
lonely), floored at 6 — she never goes completely cold.Patience — bleeds 0.1/min while a fail streak is unresolved (and the ticker keeps
femboy_report_command's damage stinging between reports), regenerates toward 55 when green.
Properties the tests pin down:
Convergence, not locking. Because each tick is
f(stored_vector, now − stored_stamp), a second server process ticking a moment later sees ~0 elapsed and its write is a no-op — multi-process decay never stacks (Phase 1 limit 1 still applies to pushes, not to decay).A quiet tick stays quiet. The
lastTickAtMsstamp is low-signal: a tick that changes nothing else bumps no revision and wakes no subscriber.deriveMoodLabelis called throughengine.relabel— the ticker cannot invent a label vocabulary the tools do not know.Trouble-only monologue. The ticker speaks exactly when it walks her into sleepy/worried/lonely (source
ticker); pretty transitions stay silent.A broken clock must not crash the pastel core — timer-path failures are contained.
The loot loop (Phase 4)
Care is a game now. Every durable event the tools already produce is tallied in meta
(headpat_total, items_total, git_commit_streak, night_events:YYYY-MM-DD,
bugs_whispered, patience_comebacks, … — src/progress/detect.ts owns the names), and six
detectors read those tallies (src/progress/achievements.ts):
Achievement | Earned when | Unlocks |
🌙 Night Owl | ≥3 tracked events inside one 02:00-05:59 night (credited once per calendar day) | skin Oversized Sleepy Hoodie |
🐾 Gentle Touch | 100 headpats, all time | accessory Cat Ears |
🌿 Git Master | 20 commits in a row with no successful force-push between | skin Cyber Maid |
🦆 Bug Whisperer | a resolved confession that was hard (≥2 duck questions, or open 20+ min, or confessed while already worried/sleepy) | accessory Round Glasses |
🧋 Sugar Rush | 10 treats handed over | accessory Headphones |
✅ Green At Last | climbing out of comfort mode back to a green build | accessory Band-aid |
Properties the tests pin down:
Detectors are pure.
measure({ counters }) → { value, target, done }— no clock, no DB. Awarding lives insyncAchievements(src/progress/awards.ts): the inventory row is the memory, so running the same evaluation twice (or from two processes on one database) converges and never fires a second toast.One batch, one commit. An unlock transaction writes the achievement row, the wardrobe pieces that name it as their price, one toast each, one celebration line (
speech,source: achievement:<id>) and one triumph diary entry — then emits a single coalesced event whose deltas coverprogress,state,speechanddiary.Rewards are derived, never declared twice.
achievements.tsaskswardrobe.tswho lists it asrequires; a test asserts every gate is an achievement and back.The rail is where the money is.
femboy://progresscarries progress + the closet with its prices + pending toasts, andfemboy://stateembedsachievements,unlockedSkins,accessoriesandtoast— so a client that only subscribes to state still repaints the closet and still gets its popup.Toasts are events, not fields. Queued with a 45 s TTL, cleared by
femboy_ack_toast(which bumpsprogress+stateso other windows stop drawing the same celebration), and forgotten by housekeeping after a day. A client that never acks is never stuck.Night watch. The ticker is what earns Night Owl on an idle night: a live terminal heartbeat inside 02:00-05:59 counts as one event per tick, then it also runs the award pass (so counters written by another process get credited within a tick) and, hourly, sweeps stale per-night tallies and expired toasts — silently, without bumping a revision.
The gate is one function.
isGatedAccessory+identify(raw, unlocked)decide what may be worn; catalogued-but-locked pieces fail with the achievement and its condition named, unknown accessory names stay free-form and become owned on first wear.
Subscriptions (the Phase 1 headline feature)
resources/subscribe / resources/unsubscribe are implemented per session, and mutations
fan out as notifications/resources/updated only to sessions that asked for that
resource. The notification is spec-clean (params.uri) and carries the differential
payload in params._meta, which the MCP types explicitly allow:
{
"method": "notifications/resources/updated",
"params": {
"uri": "femboy://state",
"_meta": {
"femboy.dev/schema": "femboy.resourceDelta@1",
"femboy.dev/revision": 7,
"femboy.dev/changedKeys": ["outfit", "features.uwu"],
"femboy.dev/actor": "stdio:98565@boxlaptop",
"femboy.dev/timestampMs": 1761900000000
}
}
}A client that was disconnected can replay what it missed: the resource_changes journal
stores every committed diff by revision (StateEngine.changesSince(uri, lastSeenRevision)).
Two properties the tests pin down:
Commit before broadcast. A mutation is persisted, journalled and committed before any notification leaves the process — nobody is woken for data that isn't there yet.
Quiet stays quiet. Heartbeat/activity stamps are written but filtered out of the diff, so a room full of terminals poking the store does not spam SSE streams. Only meaningful changes bump a revision.
Related MCP server: sthayi
Running it
npm install
npm run build
# local per-terminal mode (pi spawns this)
node dist/index.js serve --transport stdio
# shared mode for tmux windows / machines
FEMBOY_SECRET=$(openssl rand -hex 16) node dist/index.js serve --transport http --port 7413
# both at once (stdio for the parent process, HTTP for everyone else)
node dist/index.js serve --transport both
# introspection
node dist/index.js --print-config # resolved config + where each value came from
node dist/index.js status # probe /healthz
node dist/index.js --helpWiring it into pi
# stdio (simplest; one server per pi process)
pi mcp add femboy -- npm --prefix /path/to/femboy-mcp run start -- --transport stdio
# shared HTTP across all your windows (what pi-femboy-tui pairs to — see femboy.mcp.url)
pi mcp add femboy --url http://127.0.0.1:7413/mcp --bearer-token-env-var FEMBOY_SECRETOr by hand in ~/.pi/agent/mcp.json / .pi/mcp.json:
{
"mcpServers": {
"femboy": {
"url": "http://127.0.0.1:7413/mcp",
"headers": { "Authorization": "Bearer ${FEMBOY_SECRET}" },
"description": "Shared pastel vibe state for pi-femboy-tui"
}
}
}Configuration
Precedence: defaults < femboy-mcp.config.json < environment < CLI flags.
See femboy-mcp.config.example.json for a starting point.
Key | Env | CLI | Default |
transport |
|
|
|
http host |
|
|
|
http port |
|
|
|
mcp path |
| — |
|
db path |
|
|
|
bearer secret |
|
| unset (auth off) |
loopback exempt |
|
|
|
mood tick |
|
|
|
ticker on/off |
|
|
|
session reaper |
|
|
|
reaper sweep |
|
|
|
log level |
|
|
|
config file |
|
|
|
File lookup order is: --config → FEMBOY_CONFIG → ./femboy-mcp.config.json →
~/.config/femboy-mcp/config.json. ~ is expanded in paths. Bad values fail loudly at
startup instead of being coerced.
Auth model
If FEMBOY_SECRET is set, HTTP clients need Authorization: Bearer <secret> — except
loopback requests, which are exempt while allowLocalUnauthenticated is true (that's the
convenient default for your own tmux windows). Set FEMBOY_ALLOW_LOCAL_UNAUTH=0 to require
the token everywhere. Starting an HTTP transport on a non-loopback host with no secret
prints a warning. http.allowedHosts turns on the SDK's DNS-rebinding protection.
Extra HTTP endpoints
GET /healthz— liveness, session count, current revisions, broadcast counters, ticker status.GET /healthz?deep=1— addsPRAGMA quick_check, memory stats, inventory and presence counts.GET /— name/version/endpoint discovery.
FEMBOY_READY_FILE makes the server dump {pid, http: {host, port, path}} once it is
bound — handy when you start it with --port 0.
Presence hints
HTTP clients may send x-femboy-client, x-femboy-tmux-pane, x-femboy-window-title and
x-femboy-pid. They land in presence_sessions, so femboy://presence can name the
window the chibi should actually be drawn in. Without hints the id is http:<clientInfo.name>:<session8>.
Architecture
src/
index.ts CLI entry, lifecycle, graceful shutdown, ready file
cli.ts argument parsing + help
config.ts layered config (file < env < CLI), validation
log.ts stderr-only logger (stdout is the stdio JSON-RPC channel)
constants.ts version + resource URIs
mood/
labels.ts pure deriveMoodLabel() + per-label render/tone profiles
effects.ts headpat curve, item catalog, confession/relief deltas, command reports, duck nudges
rhythm.ts Phase 3 biorhythm physics: elapsed-time decay, circadian phases, transition lines
progress/
detect.ts Phase 4 detectors, pure: git-command classification, 02:00-05:59 nights,
hard-bug tests, counter names
achievements.ts the catalog: six scored detectors, celebration lines, toast copy
wardrobe.ts the closet: skins/accessories, tier, which achievement buys each
awards.ts orchestration: observe* → syncAchievements → progressView → housekeeping
state/
types.ts domain types: VibeState, MoodSnapshot, PresenceSnapshot, SpeechLine,
DiaryEntry, BugConfession, Toast, ResourceDelta
engine.ts StateEngine: transactions, revisions, diff journal, listeners, snapshots
service.ts VibeService: the tool use-cases (one transaction per call)
ticker.ts MoodTicker: the background heartbeat tick (Phase 3 decay + relabel + mutters)
outfits.ts display name ⇄ ref_id canonical mapping
store/
schema.ts ordered migrations (PRAGMA user_version)
db.ts FemboyStore: WAL, busy_timeout, IMMEDIATE transactions, checkpoint
repositories.ts state / mood / presence / inventory / memory / revisions
streams.ts speech buffer / diary / bug confessions / event counters
toasts.ts Phase 4 popup rail: push / pending / ack / prune
mcp/
resources.ts resource registry (URI → snapshot builder)
tools.ts the tool surface, Phases 2-4 (zod v4 input schemas, structuredContent)
prompts.ts companion_persona + rubber_duck assembled from live state
subscriptions.ts per-session subscription sets + notification envelopes
session.ts one McpServer per connection, subscribe/unsubscribe handlers
hub.ts fan-out fabric: engine event → matching sessions → SSE/stdio
transport/
stdio.ts StdioServerTransport + stdin-EOF lifecycle
http.ts StreamableHTTPServerTransport, one session per Mcp-Session-Id,
idle/background tracking + reaperData flow of a tool call
caller (MCP tool / ticker / transport lifecycle)
└─ VibeService.headpat(...) one use-case = one transaction
└─ StateEngine.transact([moodSpec, speechSpec], { side: counters })
├─ store.transaction ── IMMEDIATE txn
│ ├─ repository.write() → changedKeys[]
│ ├─ drop low-signal keys, sort + dedupe
│ └─ RevisionRepository.bump(uri, keys, actor) → resource_meta + resource_changes
├─ commit
└─ emit one coalesced {deltas:[mood, speech]}
└─ FemboyHub.route → SessionSubscriptions.match → session.deliver
└─ notifications/resources/updated (+_meta diff)Storage layout (SQLite, WAL, busy_timeout = 5000):
state/mood_metrics— singleton rows (CHECK (id = 1)), JSON columns for flags/meta.inventory_and_unlocks—(kind, ref_id)unique; kindsitem|skin|achievement|accessory.memory_log— deduped facts with confidence, use counts,superseded_bychain.presence_sessions— one row per client id, heartbeat + focus +mode.speech_buffer— rolling live line (TTL) with history kept for replay.diary_entries— monologue withmood_tag+ JSONcontext+ client attribution.bug_confessions— rubber-duck log:open → thinking → resolved|abandoned, nudge tally, mood before.resource_meta— the single authority for "which revision of this URI is live".resource_changes— append-only diff journal, replayable by revision, prunable.toasts— Phase 4 popup rail:kind,ref_id, title/body/emoji, TTL,acked_at_ms.meta— key/value counters (headpat_total,item_total:<id>,git_commit_streak,bugs_whispered,night_events:YYYY-MM-DD, …) — the durable truth the detectors read.
Known limits (by design)
Notification fan-out is per server process. Multiple processes sharing one SQLite file (e.g. several
--transport stdioservers) read each other's committed state on the next read — and can replayresource_changesby revision — but are not pushed each other's notifications. Sharing one HTTP server (or, later, a change-tail poller) is the Phase 5 answer, which is exactly what multi-window tmux should use.Achievements are credited by the process that observed them (or by its ticker). The award pass is idempotent and reads shared counters, so a second server on the same file still earns the badge — it just may do it one tick later than the first would have. Until Phase 5 there is no cross-process nudge, so a celebration can arrive slightly out of order across windows that each run their own stdio server.
tickIntervalMsis stored but unused— shipped in Phase 3. The background ticker decays the vector on real elapsed time and relabels through the samederiveMoodLabelthe tools use. Its clock is server-local: run the shared HTTP server in the user's timezone (per-client circadian offsets would need Phase 5 presence hints).Presence honesty has two stages: last stream closed →
idle(≈5 s grace), full silence pastsessionIdleMs→detached. A crashed process is therefore never shown as an active terminal for longer than the idle budget.
Development
npm run typecheck # tsc --noEmit (src)
npm run build # → dist/
npm test # tools/check.sh: typecheck src+test, build, unit, wire, spawned E2E
node tools/poke.mjs # hands-on tour: subscribes to everything, calls the tools, prints the diffs
node tools/poke.mjs --url http://127.0.0.1:7413/mcp --token "$FEMBOY_SECRET"tools/poke.mjs runs against a throwaway temp database unless you pass --db-path or
--url, so poking the surface can never overwrite the vibe state you care about.
tools/check.sh is the gate. It runs, in order:
tsc --noEmitforsrcand forsrc + test(tsconfig.test.json),a clean build,
unit + in-process integration tests (
node --import tsx --test): config precedence & validation, migrations & seeds, engine revisions/diffs/journal, presence lifecycle, memory dedupe, inventory unlocks, mood mechanics (label matrix, headpat saturation, item effects and overrides, duck nudges being questions), rhythm & command reports (circadian phases, elapsed-time decay, ignore cooling, patience bleed/heal, comfort-mode threshold, Ctrl+C forgiveness), the ticker (silent stamping, low-signallastTickAtMs, trouble-only mutters, multi-process convergence, contained broken clocks, thefemboy_report_commanduse-case end to end), streams (speech TTL, diary ordering/context, confession lifecycle, counters), the loot rail (git-command and night-window classifiers, hard-bug rules, catalog/wardrobe consistency, one-shot idempotent awarding, toast TTL + ack, gated wardrobe, the ticker's night watch and housekeeping), the service use-cases (one transaction per call, rejected operations leave no trace), and the subscription matrix over linked in-memory transports (per-resource routing, idempotent subscribe, unsubscribe, URI normalisation, unknown-URI rejection, dead-stream containment, coalesced multi-resource mutations, idle/wake broadcasts),the whole tool + prompt surface over the wire (
test/tools.test.ts): advertised schemas, structuredContent, tool errors with hints, malformed calls refusing to write, remember→recall round-trips, diary and speech visible through their resources, the confession lifecycle, diff replay, the Phase 4 rail (gated accessory refused, the git chain built over the wire, unlock notifications reachingprogress+statesubscribers, toasts acked away, achievements insidecompanion_persona), and both prompts rendered from live state,tests against the spawned server binary: stdio (stdout purity, shared durability, detach-on-EOF, child exits) and streamable HTTP (two real clients, cross-session broadcast of a headpat performed by the other window, DELETE termination, prompt access over HTTP, 401/bearer paths, unknown-session 404, backgrounding on stream close, idle reaper).
Tests use real SQLite files in $TMPDIR and real node child processes — nothing is mocked
at the transport boundary, because the transport boundary is the whole point of Phase 1.
Adding a reactive resource
add the URI to
RESOURCE_URIS(src/constants.ts),add a table/columns in a new migration (
src/store/schema.ts),add a repository write that returns
changedKeys,register it in
RESOURCE_DEFINITIONS(src/mcp/resources.ts) and give the engine a snapshot builder that includesschema+revision,map URI → table in
URI_TABLE(src/state/engine.ts) if it has low-signal fields,add a use-case on
VibeService(onetransactcall) and expose it as a tool.
Subscriptions, diffing and fan-out need no changes — that is the point of Phase 1.
The one rule
pastel never breaks pi: no stderr banner may ever reach stdout, no notification may be
sent for uncommitted data, a rejected operation must leave no trace, and a dead client stream
must never take the shared server down. All four are asserted by the test suite.
MIT © ArtemidaGre
Available Tools
14 toolsfemboy_ack_toastAcknowledge a toastAIdempotent
Take an achievement popup off the rail after the status line has displayed it. Acks are real mutations: they bump femboy://progress and femboy://state so the other windows stop drawing the same celebration twice. Toasts also expire on their own, so a client that never acks is never stuck.
Inputs: all, toast_id.
femboy_changes_sinceWhat changed since revision NARead-onlyIdempotent
Replay the differential change journal for a resource — for a client that was offline and missed notifications. Returns revisions and the keys each one touched (the same payload notifications carry in _meta).
Inputs: uri (required), since_revision (required), limit.
femboy_confess_bugConfess a bug (rubber duck)A
Tell the companion about a bug. She does NOT solve it: she logs the confession, reacts, writes it in her diary, and asks one socratic question back. Call again with confession_id + status "resolved" when it is fixed (that restores patience a lot and clears the fail streak). A resolution that took two or more questions, or stayed open for 20+ minutes, earns the "Bug Whisperer" achievement and the Round Glasses accessory.
Inputs: status, hypothesis, confession_id, problem_description.
femboy_give_itemGive an itemA
Feed or treat the companion. Effects: boba_tea (+energy, +affection), monster_energy (big +energy, −patience, and pointless if she is already wired), snack (small everything positive). Adds to the inventory count.
Inputs: item_id (required).
femboy_headpatHeadpatA
Give the companion a headpat. Raises affection (and a little patience), costs a whisper of energy, and she reacts out loud on femboy://speech/live. Strongly saturates near 100 affection. Every pat is tallied: 100 of them unlock the "Gentle Touch" achievement and the Cat Ears accessory.
Inputs: note, intensity.
femboy_progressAchievements & closetARead-onlyIdempotent
Read the loot rail: every achievement with how close it is, the whole wardrobe with the price tag on each locked piece, the pending status-line toasts, and the counters behind them. Read-only — unlocks themselves happen inside the care tools (headpat, gift, command reports, confessions) and the ticker.
Inputs: view, only_locked.
femboy_recallRecall factsARead-onlyIdempotent
Read the memory log: substring search plus optional category filter, ordered by confidence then recency. Read-only counterpart of femboy_remember_fact.
Inputs: limit, query, category.
femboy_remember_factRemember a factAIdempotent
Persist a short fact about the user or the project into the memory log. Repeated identical facts raise confidence instead of duplicating. Facts feed the companion_persona prompt.
Inputs: fact (required), category (required), confidence.
femboy_report_commandReport a terminal commandA
The environment hook: tell the companion how a shell command ended (raw exit code). Failures raise the streak and bite patience; 3+ in a row switches her into sympathy mode (comfort line, diary note, worried tone). A success heals patience (more, the longer the broken streak was) and clears the streak; 130/143 (Ctrl+C/SIGTERM) reset it without penalty. Phase 4 reads the same reports for loot: git commit extends the Git Master chain, a successful git push --force ends it, climbing out of comfort mode to green earns "Green At Last", and command reports after 02:00 feed "Night Owl". Wire this from a pi shell hook or the TUI launcher.
Inputs: exit_code (required), command.
femboy_report_gitReport a git eventA
The git hook: tell the companion a commit landed (or that history was rewritten). Feeds the Git Master chain — 20 commits in a row without a successful force-push unlocks the Cyber Maid skin. A plain femboy_report_command carrying a git command line is classified the same way, so wiring both is harmless.
Inputs: action (required), ok, detail.
femboy_saySay somethingA
Push a line into the live speech buffer so the TUI can animate the mouth and thought bubble (femboy://speech/live). Lines expire (default 20 s).
Inputs: text (required), kind, ttl_ms.
femboy_set_outfitSet outfitAIdempotent
Dress the chibi: change the current outfit and optionally the accessory. Accepts a display name ("Pastel Sailor") or a ref_id ("pastel_sailor"). Starter skins are always wearable; rare and legendary ones cost an achievement (femboy_progress). Catalogued accessories are gated the same way, free-form ones are worn and registered as owned.
Inputs: outfit (required), accessory.
femboy_toggle_featureToggle pastel featureAIdempotent
Turn a pastel feature flag on or off (uwu transcript, chrome takeover, companion, headpat counter…). Flags are free-form keys stored in femboy://state.
Inputs: enabled (required), feature (required).
femboy_write_diary_entryWrite diary entryA
Add a line to the companion’s inner monologue (femboy://diary). Use for reflections about the code, small wins, or mood notes — not for facts (use femboy_remember_fact).
Inputs: entry (required), mood_tag.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 14 tools
Most tools map to a distinct action (feeding, headpats, outfit, diary, facts, confessions, toasts, progress). The main overlap is femboy_report_command vs femboy_report_git, which the descriptions admit are classified identically, and femboy_say vs femboy_write_diary_entry sit close together (live speech vs inner monologue) but are adequately differentiated.
Every tool uses the same femboy_ prefix with snake_case, and nearly all follow verb_noun (give_item, write_diary_entry, set_outfit, toggle_feature, remember_fact, report_git). Two bare-verb outliers, femboy_say and femboy_recall, are minor deviations that remain readable.
Fourteen tools is on the upper edge for a companion/novelty server, but each covers a genuinely different mechanic (care, memory, diary, speech, achievements, hooks, change journal). The report_git/report_command split is the one arguably redundant addition.
The surface covers the domain well: care actions, memory write+read, diary write, achievements read+ack, feature flags, bug confession open/resolve, git and shell hooks, and differential replay. Minor gaps exist: diary entries and the wardrobe/inventory can be read via resources or femboy_progress but not directly as tools, and there is no explicit state/inventory getter.
Related MCP Connectors
Persistent memory for AI assistants — one shared, OAuth-secured vault for every MCP client.
Persistent, portable memory for AI assistants — your private memory graph, from any MCP client.
One memory, every AI. A shared, user-owned markdown memory your AI clients read and write over MCP.
Cloud-hosted MCP server for durable AI memory
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceProvides a selective persistent memory layer for AI companions, enabling structured recall, reinforcement, and time-decayed retrieval through an MCP interface.8 npm1MIT
- AlicenseNot gradedqualityCmaintenanceA local-first MCP server that gives AI clients a shared, user-owned memory and skills plane, enabling persistent memory across apps via the Model Context Protocol.MIT
- FlicenseNot gradedqualityBmaintenanceEnables MCP-compliant clients to run a personal AI copilot with agenda tracking, autonomous sandbox execution, ambient cue handling, fast reflex actions, and episodic memory.7-
- FlicenseNot gradedqualityCmaintenanceA long-term memory MCP server for AI companions that lets them store what the user says, let memories fade by importance, emotion and time, and automatically surface relevant ones through semantic recall, people cards, dates, letters and sticky notes. It also provides hybrid vector-plus-keyword search and memory management tools (wake, recall, remember, update, pin, forget) over stdio or authenticated HTTP with a Docker/Postgres backend and web memory globe.-