AGENTS.md
+27 −23
https://raw.githubusercontent.com/cyanheads/shift-mcp-server/HEAD/AGENTS.md
− **Version:** 0.1.8− **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.13`− **MCP SDK:** `@modelcontextprotocol/server` ^2.2.0− **Zod:** ^4.6.5− - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.− import { formatWorkersTable, type WorkerSession, workers } from '@/services/worker-store/worker-store.js';− const session: WorkerSession = upsertSession(input);− // Every mutation of the roster notifies shift://status subscribers.− ctx.notifyResourceUpdated?.('shift://status');− return { workerId: session.workerId, activeWorkers: [...workers.values()] };− import { formatWorkersTable, workers } from '@/services/worker-store/worker-store.js';− if (workers.size === 0) return '## Active Workers (0)\nNo agents are currently active.';− return `## Active Workers (${workers.size})\n${formatWorkersTable([...workers.values()])}`;− No server-specific environment variables — worker sessions live in memory and clear on restart, so there is no `src/config/server-config.ts`. Add one (`parseEnvConfig` over a Zod schema) only if a real knob appears; use `z.stringbool()` for env booleans, never `z.coerce.boolean()`.− resources: [statusResource],− This server declares `sessionMode: 'stateless'` and neither hook: no tool calls `ctx.requestInput`, and the worker roster is a plain in-memory `Map` with nothing to release.− | `ctx.notifyResourceUpdated` | Signals that `shift://status` changed. Called on every check-in, session update, and check-out. Subscription-scoped — see `framework-skills/api-context`. |− `ctx.state`, `ctx.requestInput` / `ctx.inputs`, `ctx.enrich`, and `ctx.content` are unused here — the worker roster is a module-level `Map`, not tenant-scoped storage. `framework-skills/api-context` documents the full surface if one becomes relevant.− worker-store.ts # In-memory session Map, ID generation, table formatting− - [ ] Any change to the roster (check-in, session update, check-out) calls `ctx.notifyResourceUpdated('shift://status')`− - [ ] `workerId` inputs keep the `WORKER_ID_PATTERN` regex, so a malformed ID is rejected before the handler runs− - [ ] Every field rendered into the active-workers table appears verbatim — `format-parity` walks nested array fields, so a truncated or reformatted value fails `lint:mcp`− - [ ] Tests clear `workers` in `beforeEach` — the store is module-level and persists across cases+ **Version:** 0.2.0+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.15`+ **MCP SDK:** `@modelcontextprotocol/server` ^2.3.1+ **Zod:** ^4.6.7+ - **Use `ctx.state`** for tenant-scoped KV. The workspace roster uses its transactional `WorkerStore` service; handlers never issue SQL directly.+ import { formatWorkersTable, getWorkerStore, notifyWorkspace, resolveWorkspace } from '@/services/worker-store/worker-store.js';+ const workspace = resolveWorkspace();+ const { session, activeWorkers } = getWorkerStore().checkIn(workspace, input);+ if (!session) throw ctx.fail('unknown_worker');+ // Publish through the server notifier after the transaction commits.+ notifyWorkspace(workspace);+ return { workerId: session.workerId, activeWorkers };+ import { formatWorkersTable, getWorkerStore, resolveWorkspace } from '@/services/worker-store/worker-store.js';+ const workers = getWorkerStore().list(resolveWorkspace());+ return `## Active Workers (${workers.length})\n${formatWorkersTable(workers)}`;+ `src/config/server-config.ts` parses `SHIFT_STATE_DIR` and `SHIFT_WORKSPACE` during setup. SQLite defaults to `~/.local/state/shift-mcp-server/roster.sqlite`; the default workspace is `SHIFT_WORKSPACE`, otherwise the real enclosing Git root or launch cwd. Explicit workspace selectors are opaque. Sessions persist across restarts until check-out. `docs/design.md` records transaction and namespace rules.+ resources: [statusResource, workspaceStatusResource],+ This server declares `sessionMode: 'stateless'`: no tool calls `ctx.requestInput`. Setup opens the SQLite store and captures `core.notify.resourceUpdated`; teardown closes storage. Modern subscriptions receive process-local committed mutations from either protocol era. The framework's legacy adapter and other processes require polling; this is not a restriction of the 2025 protocol.+ | `ctx.enrich.notice` | Carries the composed coordination protocol and solo notice once on both production output surfaces. |+ `ctx.state`, `ctx.requestInput` / `ctx.inputs`, and `ctx.content` are unused here. Roster mutations call `notifyWorkspace()` only after commit, replacing request-scoped notifications to avoid duplicates. It publishes the qualified URI, plus the bare alias only for the default workspace.+ config/server-config.ts # SHIFT_STATE_DIR and SHIFT_WORKSPACE+ worker-store.ts # SQLite transactions, workspace identity, table formatting+ workspace-status.resource.ts # shift://status{?workspace}+ - [ ] Actual roster mutations publish through `notifyWorkspace()` after commit; no-op check-out emits nothing+ - [ ] `workerId` inputs validate six ASCII letters/digits before uppercasing; Unicode expansion, whitespace and punctuation stay invalid+ - [ ] Table cells escape backslashes before pipes and flatten newlines; raw stored/structured values and full timestamps are preserved+ - [ ] Tests isolate SQLite state in scratch directories; never read or mutate an installed user's roster