Skip to main content
Glama

♡ 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 tests

What is exposed today

Resources (all application/json, all reactive)

URI

Payload

Notes

femboy://state

outfit, accessory, status text, activity, badge, feature flags, locale, unlocked skins/achievements/accessories, item counts, toast + toasts (the live status-line popup), progress (earned/total)

femboy.state@1

femboy://mood

energy / affection / patience vector, label + derivedLabel, labelProfile (emoji/mouth/tone), circadian (hour/phase/tick recency), comfortMode, headpat count, fail streak, interaction recency

femboy.mood@1

femboy://presence

every connected terminal, transport, pid, tmux pane, heartbeat, focus, mode (active/idle/sleeping/detached), activeClientId

femboy.presence@1

femboy://speech/live

the current line (speaking, current) + a short tail of recent lines, TTL-aware

femboy.speech@1

femboy://diary

newest-first monologue entries with moodTag + structured context, byClient, open-confession count

femboy.diary@1

femboy://progress

every achievement with value/target/ratio, its rewards, the whole wardrobe with the achievement that buys each locked piece, counters, and the pending toasts

femboy.progress@1

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

femboy_set_outfit(outfit, accessory?)

Wear an owned skin; accepts "Gothic Mochi" or gothic_mochi. null accessory takes it off. Free-form accessories are worn and registered as owned; catalogued rare ones are gated by an achievement. Refusals name the achievement and the condition (20 commits in a row…).

femboy_toggle_feature(feature, enabled)

Flip a pastel feature flag (uwu, chrome, companion, …). Name is case-normalised; junk identifiers are refused. Diff is per key: features.uwu.

femboy_headpat(intensity?, note?)

1-10 (default 3). +affection, +patience, −a little energy, saturating near the top. She reacts on the speech buffer; note becomes a memory fact; every pat is counted — 100 of them earn Gentle Touch. Returns earned[] when the call tipped a detector.

femboy_give_item(item_id)

boba_tea / monster_energy / snack with per-item effect vectors (monster energy on an already-wired companion trades energy for jittery and says so). Increments the inventory.

femboy_remember_fact(category, fact, confidence)

Writes to memory_log. Repeating an identical fact raises its confidence instead of duplicating.

femboy_confess_bug(problem_description, hypothesis?, status?, confession_id?)

Rubber-duck log. Always answers with one socratic question, never a fix. Re-call with confession_id + status:"resolved" for the relief jump (patience up, fail streak cleared, triumphant diary entry).

femboy_write_diary_entry(entry, mood_tag?)

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

femboy_progress(view?, only_locked?)

Read the rail: progress per achievement, the closet with its prices, pending toasts, the counters behind them. view is all (default) / achievements / wardrobe / toasts. Read-only.

femboy_ack_toast(toast_id?, all?)

Take a popup off the rail after the status line displayed it — a real mutation on femboy://progress + femboy://state, so the other windows stop drawing it twice. Un-acked toasts expire on their own.

femboy_report_git(action, ok?, detail?)

The git hook: commit / push / force_push / rebase. Feeds the same Git Master chain that femboy_report_command builds from parsed command lines, so a post-commit hook and a shell hook never disagree.

Three extras, because the Phase 2 resources would otherwise be unreachable or write-only:

Tool

Why it exists

femboy_say(text, kind?, ttl_ms?)

the only writer for femboy://speech/live

femboy_recall(query?, category?, limit?)

the only reader for what femboy_remember_fact wrote

femboy_changes_since(uri, since_revision, limit?)

replays the diff journal for a client that was offline

…plus the Phase 3 environment hook:

Tool

What it does

femboy_report_command(exit_code, command?)

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, worried tone through the prompts. A success heals patience (the relief scales with the streak it ended) and clears it; 130/143 (Ctrl+C/SIGTERM) reset the streak without blame. Wire it from a pi shell hook.

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 lastTickAtMs stamp is low-signal: a tick that changes nothing else bumps no revision and wakes no subscriber. deriveMoodLabel is called through engine.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 in syncAchievements (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 cover progress, state, speech and diary.

  • Rewards are derived, never declared twice. achievements.ts asks wardrobe.ts who lists it as requires; a test asserts every gate is an achievement and back.

  • The rail is where the money is. femboy://progress carries progress + the closet with its prices + pending toasts, and femboy://state embeds achievements, unlockedSkins, accessories and toast — 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 bumps progress + state so 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 --help

Wiring 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_SECRET

Or 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

FEMBOY_TRANSPORT

--transport

stdio

http host

FEMBOY_HOST

--host

127.0.0.1

http port

FEMBOY_PORT

--port

7413 (0 = ephemeral)

mcp path

FEMBOY_HTTP_PATH

—

/mcp

db path

FEMBOY_DB_PATH

--db-path

$XDG_DATA_HOME/femboy-mcp/state.sqlite

bearer secret

FEMBOY_SECRET

--secret

unset (auth off)

loopback exempt

FEMBOY_ALLOW_LOCAL_UNAUTH

--allow-local-unauth

true

mood tick

FEMBOY_TICK_MS

--tick-ms

60000 (Phase 3 ticker interval)

ticker on/off

FEMBOY_TICK_ENABLED

--tick-enabled

true (false = mood only moves via tools)

session reaper

FEMBOY_SESSION_IDLE_MS

--session-idle-ms

300000 (0 = never)

reaper sweep

FEMBOY_SWEEP_MS

--sweep-ms

15000

log level

FEMBOY_LOG_LEVEL

--log-level

info

config file

FEMBOY_CONFIG

--config

./femboy-mcp.config.json, then ~/.config/femboy-mcp/config.json

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 — adds PRAGMA 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 + reaper

Data 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; kinds item|skin|achievement|accessory.

  • memory_log — deduped facts with confidence, use counts, superseded_by chain.

  • presence_sessions — one row per client id, heartbeat + focus + mode.

  • speech_buffer — rolling live line (TTL) with history kept for replay.

  • diary_entries — monologue with mood_tag + JSON context + 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)

  1. Notification fan-out is per server process. Multiple processes sharing one SQLite file (e.g. several --transport stdio servers) read each other's committed state on the next read — and can replay resource_changes by 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.

  2. 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.

  3. tickIntervalMs is stored but unused — shipped in Phase 3. The background ticker decays the vector on real elapsed time and relabels through the same deriveMoodLabel the 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).

  4. Presence honesty has two stages: last stream closed → idle (≈5 s grace), full silence past sessionIdleMs → 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:

  1. tsc --noEmit for src and for src + test (tsconfig.test.json),

  2. a clean build,

  3. 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-signal lastTickAtMs, trouble-only mutters, multi-process convergence, contained broken clocks, the femboy_report_command use-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),

  4. 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 reaching progress + state subscribers, toasts acked away, achievements inside companion_persona), and both prompts rendered from live state,

  5. 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

  1. add the URI to RESOURCE_URIS (src/constants.ts),

  2. add a table/columns in a new migration (src/store/schema.ts),

  3. add a repository write that returns changedKeys,

  4. register it in RESOURCE_DEFINITIONS (src/mcp/resources.ts) and give the engine a snapshot builder that includes schema + revision,

  5. map URI → table in URI_TABLE (src/state/engine.ts) if it has low-signal fields,

  6. add a use-case on VibeService (one transact call) 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 tools
femboy_ack_toastAcknowledge a toastA
Idempotent

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 NA
Read-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 & closetA
Read-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 factsA
Read-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 factA
Idempotent

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 outfitA
Idempotent

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 featureA
Idempotent

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

    A4/5.0

    Scored across 14 tools

    Disambiguation4/5

    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.

    Naming Consistency4/5

    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.

    Tool Count4/5

    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.

    Completeness4/5

    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

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      Provides a selective persistent memory layer for AI companions, enabling structured recall, reinforcement, and time-decayed retrieval through an MCP interface.
      8 npm
      1
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      A 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
    • F
      license
      Not graded
      quality
      C
      maintenance
      A 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.
      -