13 KiB
Phase 06A: Thin @open-pets/mcp tools over local IPC
Goal
Make OpenPets minimally usable from MCP-capable coding agents through a thin Node/npm/npx MCP server package over the existing Phase 05 local IPC methods.
This sub-phase turns the Phase 05 local IPC control plane into the first agent-facing integration:
MCP-capable agent → @open-pets/mcp stdio server → @open-pets/client → desktop local IPC → visible pet
Non-goals
- Claude Code config detection/setup UI.
- Claude enhanced hooks.
- Agent Setup window implementation.
- Pet install/remove/default tools exposed to agents.
- Remote HTTP/TCP MCP server.
- Full packaging/release publishing.
- Complex session/project routing.
- Advanced animation engine.
- Direct app auto-install. MCP may try to launch an installed app later, but must not install it.
User-visible/manual outcome
After this phase, a developer can run the local @open-pets/mcp package as a stdio MCP server, call:
openpets_status
openpets_say
openpets_react
and see the running OpenPets desktop pet respond through local IPC.
If OpenPets is not running, the MCP server remains alive and openpets_status reports a clear degraded/unavailable status instead of crashing.
This phase is explicitly not the full Phase 06 lease lifecycle. Full real --pet routing, non-default temporary pet windows, lease heartbeat/release, fallback for missing explicit pets, and TTL cleanup are split into follow-up Phase 06B.
Acceptance criteria
@open-pets/mcpbuilds and runs with Node/npm/pnpm and is shaped for futurenpx -y @open-pets/mcpusage.- Package has a
binentry and compiled entrypoint has a Node shebang. - MCP server uses stdio transport and never writes logs to stdout.
- MCP server exposes exactly these initial tools:
openpets_statusopenpets_sayopenpets_react
openpets_statuscalls@open-pets/client.status()and reports reachable/degraded app state as both human-readable text and structured content where supported.openpets_saycalls@open-pets/client.say()with the user-provided short message and optional reaction.openpets_reactcalls@open-pets/client.react()with one allowed reaction.- MCP tool schemas enforce the public reaction list from
task.txt. - MCP tool schemas keep
openpets_say.messageshort enough for Phase 05 IPC limits and speech safety. - Tool errors return MCP tool errors (
isError: true) rather than crashing the server. - If desktop app/discovery/local IPC is unavailable,
openpets_statusreports degraded state clearly. - If desktop app/discovery/local IPC is unavailable,
openpets_sayandopenpets_reactreturn clear tool errors. - No tool can install pets, remove pets, set default pet, browse catalog, edit settings, access filesystem, run shell commands, or expose generic IPC.
--pet <id>is parsed and included in MCP status/config output.--petis clearly reported as configured-but-not-yet-routed in Phase 06A; actual tools still affect the desktop app's current default pet through Phase 05 IPC.openpets_statusclearly distinguishesconfiguredPetIdfromactualTargetPetId.- If
--petis omitted, status reports that the MCP server targets the user's current default pet. - MCP process lifecycle handlers close the MCP server cleanly on
SIGINT/SIGTERMwhere available. - No Bun runtime requirement.
- Automated checks cover MCP argument parsing, tool input validation helpers, and degraded status formatting where practical.
Scope note: Phase 06A versus 06B
The high-level Phase 06 target in docs/implementation-process.md includes immediate lease acquire/release, --pet opening explicit non-default pets, fallback for missing explicit pets, and TTL cleanup.
Current implementation state after Phase 05 provides hello, status, pet.react, and pet.say over local IPC, but does not yet expose lease.acquire, lease.heartbeat, or lease.release, and the desktop app still has only a persistent default-pet window.
Oracle plan review required this split. This spec is Phase 06A:
Phase 06A: thin MCP tools over default-pet IPC
Phase 06B: MCP lease lifecycle and explicit non-default pet windows
Do not silently expand Phase 06A into lease lifecycle work.
Proposed files/directories
Expected additions/changes:
docs/phases/phase-06a-thin-mcp-tools.md
packages/mcp/package.json
packages/mcp/src/index.ts
packages/mcp/src/args.ts
packages/mcp/src/tools.ts
packages/mcp/src/check-mcp-contract.ts
packages/mcp/tsconfig.json
packages/client/src/index.ts (only if minor client result typing is needed)
pnpm-lock.yaml
Exact file split may change if a simpler implementation is clearer.
Technical approach
MCP SDK
Use the stable official TypeScript MCP SDK for this npm/npx-facing package:
@modelcontextprotocol/sdk
zod
Do not use alpha split SDK packages unless the stable SDK proves incompatible and the reason is documented.
Server shape
- Entry file starts with
#!/usr/bin/env node. @open-pets/client,@modelcontextprotocol/sdk, and the schema validation library are runtime dependencies when used by the MCP server.- Create MCP server with name
open-pets/ package name@open-pets/mcp. - Connect using stdio transport.
- Use stderr for diagnostics only.
- Keep stdout reserved for MCP protocol.
- Register three tools with small descriptions and schemas.
- Keep tool handlers pure/testable by injecting the client/status functions where practical.
Tools
openpets_status:
- No required params.
- Returns app availability, configured
--pettarget if present, default-pet status from the desktop app, and guidance if unavailable. - Should be safe/read-only/idempotent.
- Sanitizes degraded/unavailable errors before returning them to agents; do not expose home paths, socket paths, tokens, or raw Node stack details.
Structured status shape:
interface OpenPetsMcpStatus {
ok: boolean;
appRunning: boolean;
configuredPetId?: string;
actualTargetPetId?: string;
actualTargetPetName?: string;
usingDefaultPet: boolean;
routingImplemented: false;
unavailableReason?: string;
fallbackReason?: string;
}
openpets_react:
Params:
{
"reaction": "testing"
}
Valid reactions:
idle
thinking
working
editing
running
testing
waiting
success
error
celebrating
openpets_say:
Params:
{
"message": "Working on it",
"reaction": "working"
}
Rules:
- Message max 140 characters.
- Single-line.
- No code-block-like content.
- No obvious secrets, URLs, or long path-like content.
- Optional reaction must be from the public reaction list.
The desktop IPC remains the final enforcement layer; MCP schemas are an early validation layer.
CLI args
Parse only the initial simple args:
--pet <petId>
--help
--version
Rules:
- Pet id uses the same safe id shape as catalog pets.
- Do not add
--agentor--workspace. - Do not make
--petsilently install pets. - For Phase 06A-style behavior,
--petis reported in status but may not yet open non-default pets until lease lifecycle is implemented. - If
--petis provided,openpets_statusmust explicitly say explicit pet routing is not implemented in Phase 06A and the actual target remains the desktop default pet.
Degraded behavior
@open-pets/client.status() already returns a typed unavailable result when discovery/connect fails. MCP should preserve this behavior in openpets_status.
For say/react, return isError: true with a concise message such as:
OpenPets desktop app is not running or local IPC is unavailable. Open OpenPets and try again.
Lifecycle
- Handle
SIGINT/SIGTERMby closing MCP server/transport where supported. - Keep server alive even if OpenPets desktop app is unavailable.
- Do not implement public
openpets_release.
Phase 06B lease lifecycle must later:
- Acquire on MCP startup, not first tool call.
- Heartbeat while alive.
- Release on shutdown/signals/stdin close where practical.
- Use desktop TTL fallback for orphan cleanup.
- Fall back to default pet for missing explicit pet and report fallback in status.
Risks and tradeoffs
Risk: SDK API drift
Mitigation:
- Verify current official MCP TypeScript package before implementation.
- Keep SDK wrapper code small.
- Add package-local checks.
Risk: stdout corruption
Mitigation:
- Never use
console.login MCP runtime path. - Use stderr for diagnostics.
Risk: scope creep into full lease/multi-pet lifecycle
Mitigation:
- Thin MCP tools over existing local IPC are the manually verifiable Phase 06A checkpoint.
- Lease lifecycle is explicitly Phase 06B.
Risk: agents displaying private content through openpets_say
Mitigation:
- MCP schema limits message shape.
- Desktop IPC validates speech safety again.
- Tool descriptions explicitly say not to send code, logs, secrets, URLs, or file paths.
Security/privacy notes
- MCP server is stdio only.
- No TCP/HTTP listener.
- No filesystem/shell/Electron access exposed to agents.
- Agent-facing tool surface stays minimal.
- Pet management remains user-controlled in the desktop app.
openpets_sayis for short status/personality messages only.- No Bun runtime dependency.
Test/check plan
Automated checks:
pnpm check
pnpm --filter @open-pets/mcp check
pnpm --filter @open-pets/mcp build
Phase 06A should add checks for:
- CLI arg parsing for
--pet,--help,--version, invalid args. - Reaction validation accepts only public reactions.
openpets_sayvalidation rejects overlong/multiline/code/secret/path/URL-like messages.- Degraded status formatting when client reports unavailable.
- Built output has a shebang or package bin points at the expected executable.
- MCP contract check starts the stdio server and verifies
tools/listreturns exactlyopenpets_status,openpets_say, andopenpets_react. - Contract check verifies unavailable app does not crash the MCP server.
- Contract check verifies no unexpected stdout noise outside MCP protocol messages.
Manual verification guide
-
Run desktop app:
pnpm --filter @open-pets/desktop dev -
Build MCP package:
pnpm --filter @open-pets/mcp build -
Run an MCP inspector or local MCP client against the package entrypoint.
-
Call
openpets_statusand confirm it reports OpenPets reachable/default pet info. -
Call
openpets_reactwithtestingand confirm the visible pet reacts. -
Call
openpets_saywithWorking on itandworkingand confirm the visible pet message appears. -
Quit OpenPets and call
openpets_statusagain; confirm it reports degraded/unavailable clearly while MCP remains alive.
Manual acceptance question:
Does Phase 06A pass on your machine: @open-pets/mcp runs over stdio, exposes status/say/react, talks to the running desktop app through local IPC, reports configured `--pet` as not-yet-routed, and reports degraded status when OpenPets is unavailable?
Oracle plan review
Oracle reviewed the initial Phase 06 plan and blocked calling it complete Phase 06 because real lease lifecycle and --pet routing are larger cross-cutting desktop/client work. Oracle approved the architecture only as Phase 06A.
Oracle feedback disposition
- Fixed: Renamed/split scope to Phase 06A thin MCP tools over local IPC.
- Fixed: Deferred lease lifecycle, real non-default
--petwindows, heartbeat/release, fallback, and TTL cleanup to Phase 06B. - Fixed: Required status/manual docs to clearly distinguish configured
--petfrom actual default-pet target in Phase 06A. - Fixed: Switched SDK plan to stable
@modelcontextprotocol/sdkinstead of alpha split package. - Fixed: Required runtime dependencies for
@open-pets/client, MCP SDK, and schema validation library. - Fixed: Promoted runtime dependency requirement into the package/server acceptance criteria.
- Fixed: Corrected expected spec path and Phase 06A check wording after rename.
- Fixed: Tightened package/npx acceptance: bin entry, shebang, executable compiled entry, no Bun, no stdout logs.
- Fixed: Defined structured MCP status shape.
- Fixed: Required sanitized degraded errors so local paths/tokens/raw Node details are not returned to agents.
- Fixed: Required MCP contract check for tool list, invalid inputs, unavailable app behavior, and stdout protocol cleanliness.
- Fixed: Clarified tool error semantics: unavailable app returns
isError: truefor say/react rather than crashing the server. - Deferred: Auto-launch of installed OpenPets app is explicitly left for Phase 06B or packaging phase.
Implementation review disposition:
- Fixed: Replaced POSIX-only
chmod +xbuild script with a Node-based post-build executable step that no-ops on Windows. - Fixed: Replaced POSIX
X_OKcontract assertion with shebang/bin validation. - Fixed: MCP package
checkbuilds@open-pets/clientfirst so package-local checks do not depend on stale clientdistoutput. - Fixed: Added stdio smoke contract coverage using the built
dist/index.js, verifying tool list and degradedopenpets_statuswith a missing discovery file. - Fixed: Unavailable
openpets_statushuman text now still explains configured--petand Phase 06A deferred routing.