Skip to main content
Glama
ideate-ai

ideate MCP Server

Official
by ideate-ai

@ideate/plugin

CI

The public, composable surface of ideate — a Claude Code plugin for AI-augmented software delivery.

ideate decomposes into three functions: a process record (the durable, auditable trail of what was decided and done — append-only, project-local, never curated or ranked by ideate itself), a knowledge graph (memory and retrieval over that trail — developed as a separate project, not part of this plugin), and a delegation board (how work is handed to and coordinated across agents — this plugin ships its LOCAL backend; the hosted, multi-person board is a future sibling service behind the same contract). ideate is deliberately non-prescriptive about workflow: it supplies primitives that fire mechanically, not a process you are told to follow, and it never blocks, redirects, or opines on what you do.

What this plugin provides today

This package ships the Layer-0 floor: the append-only process record and the mechanical capture/priming wiring around it. Nothing here is optional workflow — every piece below fires without requiring an agent to remember to call it.

  • An append-only process record. Every entry — a decision, a finding, a session outcome, a subagent outcome, a commit boundary, a task completion — is appended, never updated or deleted; a correction is a new record that references the superseded one. Every write passes a capture-time secret-scanning gate before anything touches disk.

  • Capture hooks. SessionEnd, PreCompact, SubagentStop, TaskCompleted, and PostToolUse (on git commit) are wired in hooks/hooks.json so records are captured mechanically as you work, with zero required tool calls. Every ideate hook is non-blocking: it exits 0, writes side effects and additionalContext only, and never blocks, denies, or halts anything the host is doing.

  • Session priming. At session start, and on subagent start, a bounded, unranked digest of the most recent process records is surfaced as additional context — recency- and scope-selected only, never scored or curated, and explicitly framed as quoted historical data rather than instructions.

  • Telemetry. Native counters for capture, priming, and failure events, inspectable with the ideate-telemetry CLI.

Related MCP server: claude-engram

Requirements

Node.js >= 22.5 must be installed and on your PATH. Claude Code ships as a native binary and bundles no runtime, so the plugin's MCP server, hooks, and CLIs — which run on Node and use the built-in node:sqlite — need Node available. Install it from https://nodejs.org (or your package manager) before installing the plugin.

Install

There are two ways to wire this plugin into a Claude Code project. Both land on the same MCP server and hooks/hooks.json — the manifests below are just two different ways of pointing Claude Code at them. This section documents the contracts (what each mechanism provides); it does not prescribe which one to use or what workflow to run once installed.

(a) Marketplace install

This repo ships .claude-plugin/marketplace.json, a Claude Code plugin-marketplace manifest listing this plugin (name: ideate, source: "./" — the repo root). From within Claude Code:

/plugin marketplace add ideate-ai/ideate
/plugin install ideate

This is the manifest-driven path — Claude Code resolves the plugin and wires .mcp.json / hooks/hooks.json for you. On first launch the plugin runs a one-time setup: because built output (dist/) is not committed, it installs its single dependency and builds itself, then the MCP server, the mechanical capture/priming hooks, and the CLIs are live. The first session may pause briefly while that runs; later sessions start instantly. If Node is missing or older than 22.5 (see Requirements above), the plugin prints a clear one-line message and otherwise does nothing — it never blocks your session.

(b) Manual wiring

For a project that wants to point at this plugin directly rather than through the marketplace resolver:

  1. Build the package: npm install && npm run build (compiles src/ to dist/; dist/ is not committed). A marketplace install runs this for you on first launch; for manual wiring you run it once yourself.

  2. Add an MCP server entry to the consuming project's .mcp.json pointing at the launcher, which runs the first-launch bootstrap before starting the server:

    {
      "mcpServers": {
        "ideate": {
          "command": "sh",
          "args": ["<path-to-this-plugin>/bin/ideate-mcp"]
        }
      }
    }

    This registers all sixteen MCP verbs: the three record verbs (record_append, record_read, record_decision) described below, the eleven board verbs in The work-state board, and the two steering verbs in Steering verbs.

  3. Wire the mechanical capture hooks by pointing the consuming project's host at this plugin's hooks/hooks.json. That file declares the actual hook shape this plugin provides — SessionStart (priming via bin/ideate-record prime), SubagentStart/SubagentStop, SessionEnd (bin/ideate-record session-end), PreCompact, and PostToolUse on git commit — each entry a command hook invoking either ${CLAUDE_PLUGIN_ROOT}/bin/ideate-record or one of the hooks/*.mjs scripts. How a consuming project performs that wiring (copying the file, referencing it, or another host-specific mechanism) is outside this plugin's contract — only the shape of hooks/hooks.json itself is.

Build / test (contributor path)

Prerequisites: Node >= 22.5.

npm install
npm run build    # compiles src/ to dist/ (gitignored)
npm test         # vitest (fork pool capped — see vitest.config.ts)

npm run typecheck (tsc -p tsconfig.test.json) typechecks every **/*.test.ts in the package with noEmit — tsconfig.json's own build excludes test files so dist/ stays exactly the compiled-source shape (no .test.js alongside it), but that means npm run build alone never typechecks a test. typecheck closes that gap without touching dist/, and runs automatically before npm test/pnpm run test via the pretest script — a test file with a type error fails before vitest ever starts.

npm run test:fresh-copy runs scripts/fresh-copy-check.mjs, which copies this directory to a scratch location with no surrounding project context and re-runs install/build/test there — the mechanical proof that this package stands alone.

The process-record surface

The record core has exactly two transports over one implementation: three MCP tools, and a CLI. Both write through the same gated append-only store, so a record captured via one transport is indistinguishable from one captured via the other.

MCP verbs (registered by the ideate MCP server, dist/server.js):

  • record_append(kind, claim, verification_anchor?, scope?, content, task_id?, supersedes?, references?) — append one process record. Open-vocabulary kind (e.g. finding, session-outcome, commit-boundary, …). supersedes takes the id of the record this one replaces (a correction is a new record, never an edit); references is the advanced form — a JSON array of typed edges, [{"rel":"refutes","id":"01…"}].

  • record_read(scope?, id?, include_content?, limit?, cursor?) — read records newest-first, optionally filtered by a plain substring match against scope/kind/source or by exact id. Unranked: selection only, no scoring. Returns {ok, records, next_cursor}. Rows are summaries — every field except the prose body, plus a derived content_length; include_content: true adds the body, and an id with include_content: true is the single-record fetch. Paged: at most limit rows (default 100, clamped into 1..500) and at most ~40,000 characters of rows, so a page can come back shorter than limit while records remain — only a null next_cursor means exhaustion. Pass a page's next_cursor back as cursor (opaque; tied to the filter it was issued for) to walk a selection to the end.

  • record_decision(claim, rationale?, verification_anchor?, scope?, task_id?, supersedes?, references?) — sugar for record_append(kind="decision", ...); the ADR entry point. The decision write is its capture — there is no separate decision store, and an overturned decision is superseded, not rewritten.

The record FILES are the export surface. Each record is one Markdown file at <record.path>/YYYY/MM/{ULID}.md (record.path from .ideate.json, default .ideate/record/) — one record per file, never rewritten (files are written exclusive-create), with the YYYY/MM shard derived from the record id's own embedded timestamp, so the path of a record is computable from its id alone. An external consumer — a knowledge-graph ingester, a backup, a grep — reads that tree directly: durable, stably addressed, and requiring no ideate process, no MCP session, and no cooperation from this plugin at read time. record_read (and ideate-record read) is the in-session view for an agent: a bounded, paged, unranked selection over those same files, not an export API — do not build an ingester on it, and never read a short page as the end of the record.

ideate-record CLI (bin/ideate-record, the same gated core as a standalone executable — this is what the capture hooks invoke):

  • ideate-record append --kind <k> --claim <c|-> [--anchor <a>] [--scope <s>] [--content <text>|-] [--task <id>] [--supersedes <id>] — append one record directly; exits 1 on failure. --content - reads the prose body from stdin, and --claim - reads the claim from stdin the same way — but only ONE of the two may claim stdin per call.

  • ideate-record read [--scope <substring>] [--id <ulid>] [--limit <n>] [--cursor <c>] [--include-content] [--json] — print records newest-first; exits 1 on failure. --json is the agent-facing door and is bounded exactly like record_read (summary rows, default page size, opaque next_cursor, shared payload budget); --include-content puts the prose bodies back and requires --json. The human-readable listing is unpaged and full-bodied unless you pass --limit or --cursor. There is no "print everything" flag — the record files above are the export surface for that.

  • ideate-record session-end — reads a SessionEnd hook payload from stdin and appends a recall-shaped session-outcome record. Hook path: always exits 0 (a capture failure must never look like a hook failure to the host).

  • ideate-record prime [--scope <substring>] [--budget <n>] — print a compact, unranked digest of the most recent records for hook additionalContext. Hook path: always exits 0.

The work-state board (local backend)

The delegation board's LOCAL backend — the ratified work-state contract implemented over SQLite in WAL mode. One sentence of model: work items carry an opaque spec payload the board never parses (bring any methodology — a plan document, a Spec Kit URI, a plain prompt); claims are server-authoritative leases with fencing tokens — claim is an atomic compare-and-set that succeeds only on an open item whose dependencies are all done, leases expire (default hours-scale) so crashed workers can never orphan work forever, and a stale token is rejected on renew/complete/release after a reclaim. Every transition appends an immutable audit event in the same transaction. For a solo user the coordination features are degenerate (contention never occurs), never absent — the same code paths a future hosted team would exercise, proven by a contention suite that races real OS processes.

Eleven MCP verbs (same server, dist/server.js): work_create, work_get, work_list, work_update_meta, work_claim, work_renew, work_release, work_complete, work_cancel, work_reopen, work_events. renew/complete/release take no actor — the token proves identity, and the audit event carries the claim's actual holder.

ideate-work CLI (bin/ideate-work): the same eleven verbs as subcommands plus a CLI-only sweep (the session-boundary expiry pass the SessionStart/SessionEnd hooks trigger opportunistically). Board location: work_state.path in .ideate.json (default .ideate-work/). Every subcommand except sweep exits 1 on failure; sweep is a hook path and always exits 0, printing nothing to stdout.

  • ideate-work create --title <t> --spec <s|-> --spec-format <f> --human <h> [--agent <a>] [--depends-on <id1,id2,...>] [--supersedes <id>] [--parent <id>] [--tenant <t>] — create one item; prints it as JSON. --spec - reads the spec body from stdin — the same convention ideate-record append --content - established. --supersedes <id> records a supersedes edge to the item this one replaces, and the superseded item surfaces the replacement as a derived referenced_by backlink. --parent <id> sets the CONTAINMENT parent — a different edge from --depends-on, which is ordering; omit it for a root item.

  • ideate-work get --id <id> [--json] — fetch one item, running the lazy-expiry seam first; a miss prints (not found), or null under --json. This is the way to read one item's full spec.

  • ideate-work list [--tenant <t>] [--status <open|in_progress|done|cancelled>] [--json] [--include-spec] [--limit <n>] [--cursor <c>] — list items with the derived claimability view attached. Rows are summaries — every field except the opaque spec body, plus a derived spec_length; --include-spec puts the bodies back and requires --json (the human listing has nowhere to print them). --json returns a PAGE, not the board: {"items": [...], "next_cursor": ...}, at most --limit items (default 100, clamped into 1..500) and at most ~40,000 characters of rows — the same payload budget the MCP work_list tool applies — so a page can come back shorter than --limit while items remain, and only a null next_cursor means exhaustion. Pass a page's next_cursor back as --cursor (opaque; tied to the --tenant/--status filter it was issued for). The human-readable listing is one line per item, unpaged and unbudgeted unless you pass --limit or --cursor, in which case it prints a resume hint while items remain.

  • ideate-work update-meta --id <id> --expected-version <n> [--title <t>] [--spec <s|->] [--spec-format <f>] [--depends-on <id1,id2,...>] [--supersedes <id>] [--parent <id>] [--clear-parent] [--confirm-shrink] — update metadata via optimistic compare-and-set on version. --spec - reads the new spec body from stdin. The containment parent is tri-state: pass neither flag to leave it unchanged, --parent <id> to set or move it, --clear-parent to make the item a root again. The two are mutually exclusive. Shrink guard: replacing a spec of 200+ characters with one under 20 characters is refused (almost never intentional — this is the shape of finding 01M2MKGS5PRV7WSD0W4ZQYAG4A) unless --confirm-shrink is passed; never triggered on create.

  • ideate-work claim --id <id> --human <h> [--agent <a>] [--lease-ms <n>] — claim an open, claimable item; mints the fencing token the next three subcommands require.

  • ideate-work renew --id <id> --token <n> [--lease-ms <n>] — extend an active claim's lease. No actor flags — the token proves identity.

  • ideate-work release --id <id> --token <n> [--note <n|->] — hand an active claim back to open. No actor flags. --note - reads the handoff note from stdin.

  • ideate-work complete --id <id> --token <n> [--note <n|->] — complete an active claim. No actor flags; the note becomes a process record (below). --note - reads the completion note from stdin.

  • ideate-work cancel --id <id> --human <h> [--agent <a>] — cancel an item from open or in_progress; voids any active claim.

  • ideate-work reopen --id <id> --human <h> [--agent <a>] — move an item from done back to open.

  • ideate-work events --id <id> [--json] — every event for one item, oldest first.

  • ideate-work sweep [--tenant <t>] — CLI-only (never an MCP tool): the opportunistic board-wide expiry pass. Hook path: always exits 0, stdout stays silent, diagnostics go to stderr.

Board operations. The board is one SQLite file, board.db, under work_state.path (workStatePath(), default .ideate-work/) — nothing else lives there. Deleting that directory resets the board: every claim and event is gone, and item state re-derives from nothing (there is no recovery). This is safe to do on purpose because it is a DIFFERENT store from the append-only process record (record.path, default .ideate/record/) — deleting the board never touches the record, and vice versa. board.db also carries a schema version (PRAGMA user_version, checked on every open). Three things can happen when it does not match this binary's own version: an older, pre-versioning board is handled with a one-time grace (stamped on their next write) rather than rejected outright; a board newer than this binary but still within a compatibility floor its writer stamped into the file opens successfully but DEGRADED — anything the newer schema added is invisible to this binary — with a one-time-per-process warning on stderr and a durable note in the project's own process record (plus a telemetry counter tallying every such open), so an installed plugin lagging behind a newer board is quietly noticed rather than silently wrong; and a board newer than both this binary and its compatibility floor (or with no floor stamped at all) produces a hard version-mismatch error — that one is deliberate, not a bug, and this project makes no promises about migration timelines beyond it.

One item's lifecycle — an example trace. The transcript below runs a work item end-to-end through the board (outputs trimmed for width). It illustrates the shape of the lifecycle — it does not prescribe a workflow, and every verb is equally available over MCP:

$ ideate-work create \
    --title "Add retry backoff to the fetch client" \
    --spec "<the work-item body — opaque to the board>" \
    --spec-format "ideate/wi-v1" --human dan
{"id":"01KXBQDD7P…","status":"open","version":1,…}

$ ideate-work claim --id 01KXBQDD7P… --human dan --agent claude-coordinator
{"status":"in_progress","claim":{"holder":{"human":"dan","agent":"claude-coordinator"},
 "claim_token":1,"lease_expires":"2026-07-12T21:53:58.767Z"},…}

# …the actual work happens here…

$ ideate-work complete --id 01KXBQDD7P… --token 1 \
    --note "Exponential backoff added with jitter; full suite green."
{"status":"done","claim":null,…}

$ ideate-work events --id 01KXBQDD7P…
2026-07-12T17:53:45.206Z create actor=dan
2026-07-12T17:53:58.767Z claim actor=dan token=1
2026-07-12T17:55:58.126Z complete actor=dan token=1 note="Exponential backoff added…"

Completing with a note is also a capture point: the note becomes an append-only process record (kind work-completion, verification anchor board:<item>#complete@<time>), retrievable through the record surface like any other record:

$ ideate-record read --scope <item-id> --json
{"records":[{"kind":"work-completion",
  "verification_anchor":"board:01KXBQDD7P…#complete@2026-07-12T17:55:58.126Z",
  "claim":"Add retry backoff to the fetch client — Exponential backoff added…",…}],
 "next_cursor":null}

That --json output is a PAGE, and the real one is indented — the transcript above is trimmed for width. Read next_cursor, not the row count: a null is the only statement that the selection is exhausted.

Steering verbs

A smaller seam rides the same MCP server (dist/server.js). It keeps its own store, separate from the record and the board, and has no CLI — these two verbs exist over MCP only.

Two steering verbs — steering_read and steering_put — GATED OFF by default. Steering items are a project's guiding principles and policies, one file per item under .ideate/steering/. steering_put creates or amends ONE item: on amend the prior version is appended to the item's amendment history and the status may flip, and there is no hard delete — you deprecate via status (active | deprecated | superseded), or name a DIFFERENT item as supersedes to replace it. steering_read is selection only — by id (exact), domain (substring), status and kind — unranked by contract, and bounded the same way the reads above are: the amendment history is projected away unless you pass include_history (a history_length is always present), and the result {ok, items, next_cursor} is a page of at most limit items (default 100, clamped into 1..500) within the same ~40,000-character payload budget, resumed with an opaque cursor.

Both steering verbs are gated behind steering.enabled in .ideate.json — absent by default, which means off, with no environment override. While the gate is off — the only state this package ships in — each verb returns {"ok":false,"code":"GATED",...} as a tool error and writes NOTHING: the gate is checked before arguments are validated, so a gated project cannot even create the steering directory by calling with bad arguments. Steering shapes what a model attends to, and nothing that shapes attention ships live here ahead of the evaluation that measures it.

Honest status

  • Available now: the append-only process record, the five mechanical capture points (SessionEnd, PreCompact, SubagentStop, TaskCompleted, PostToolUse on git commit), session/subagent priming, the capture-time secret-scanning gate, native telemetry counters, and the work-state board's local backend (the eleven verbs above, with a contention suite racing real OS processes as its correctness evidence).

  • Not yet built: the hosted delegation board (cross-machine, multi-person coordination). Its ratified trigger is a concrete second contributor; the local board implements the identical contract, so that move is configuration, not a rewrite.

  • Present but gated off: the two steering verbs. They are registered by the shipped server and answer every call with {"ok":false,"code":"GATED",...} until steering.enabled is set to true in .ideate.json — see Steering verbs.

  • Present but off: claim-time priming — the hook point exists in the claim path and a work_claims telemetry counter records the denominator, but priming itself is mechanically disabled (work_state.claim_priming config flag, default off, no environment override) pending further validation. Same discipline as the rest of the deferred set: planning-time gap identification (designed, not built) and per-prompt priming (deferred). None of this plugin's shipped behavior depends on any of these.

  • This package is "private": true in package.json. It is distributed as a Claude Code plugin — a git-source marketplace install that builds itself on first launch — not published to npm, so private stays set.

License

AGPL-3.0-only — see LICENSE.

Maintenance

ActivityActive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers