MCP tools
The PLUR MCP server (@plur-ai/mcp) registers 40 tools: 39 regular tools plus plur_admin, a dispatch tool used by the reduced Cursor profile (see Tool profiles below). These are the same tools the OpenClaw plugin, Hermes adapter, and CLI delegate to under the hood — same names, same parameters, same return shape.
Tools are grouped below by lifecycle: session → memory → episodes & history → ingestion → meta → tensions → system → packs → stores & scopes → profiles.
Session lifecycle
Section titled “Session lifecycle”plur_session_start
Section titled “plur_session_start”Start a session and inject relevant engrams for the upcoming task. This is the most important tool — it’s the one that makes PLUR feel like memory rather than a tool the agent has to think about.
plur_session_start(task: string, tags?: string[], default_scope?: string) → SessionStartResultWhen agents call it: at the start of every conversation. Adapters with lifecycle hooks (Claude Code via SessionStart, OpenClaw via bootstrap, Hermes via pre_llm_call) call it automatically. Direct callers should call it as the first tool invocation per task.
What happens server-side: flushes any queued remote writes (outbox), runs hybrid injection against the task (falling back to BM25 if the embedder is unavailable), formats the result into a ## DIRECTIVES / ## CONSTRAINTS / ## ALSO CONSIDER block, and returns it with the injected engram IDs for downstream feedback. It also detects a project scope from .plur.yaml, lists writable remote-store scopes (with their descriptions and covers topics), and warns loudly when an Enterprise store is unreachable or its token has expired.
Return shape:
{ session_id: string, engrams: { text: string, // ready-to-paste system-prompt block count: number, injected_ids: string[] // for plur_feedback later }, store_stats: { engram_count: number, episode_count: number, pack_count: number }, guide: string, // instruction back to the agent remote_scopes?: Array<{ scope, url, description?, covers? }>, default_scope?: string, // from caller or .plur.yaml version_warning?: string}Gotchas:
- Calling
plur_session_starttwice in the same conversation is harmless but produces a new session ID — episodes can get split across IDs. Prefer one start per task. default_scopesets the session-wide fallback scope forplur_learn. Usually leave it unset and passscopeper engram instead — one session’s learnings rarely all belong in one store.
plur_session_end
Section titled “plur_session_end”End a session, capture summary and learnings.
plur_session_end( summary: string, engram_suggestions: Array<{ statement, type? } | string>, session_id?: string) → SessionEndResultWhen agents call it: before the conversation wraps up. engram_suggestions is a list of typed assertions the agent learned during the session (“user prefers terse responses,” “this codebase uses Caddy not nginx,” …). Each suggestion is saved through the same path as plur_learn (dedup applies), and a final episode is captured for the session.
Gotchas:
- Don’t dump every thought as a suggestion. The bar is “I’d want this engram on Monday morning.” Filter aggressively before calling.
- Suggestions should be
{statement, type}objects; bare strings are tolerated and treated as{statement}. - Returns
engrams_created,episode_id, andtotal_engrams— plus a hint if zero engrams were captured.
Memory operations
Section titled “Memory operations”plur_learn
Section titled “plur_learn”Save an engram. The explicit write path, parallel to the CLI’s plur learn.
plur_learn( statement: string, type?: "behavioral" | "terminological" | "procedural" | "architectural", domain?: string, scope?: string, tags?: string[], rationale?: string, commitment?: "exploring" | "leaning" | "decided" | "locked", pinned?: boolean, source?: string, locked_reason?: string, valid_from?: string, // ISO date — engram inactive before this valid_until?: string, // ISO date — engram expires after this supersedes?: string[] // engram IDs this statement intentionally replaces) → LearnResultWhen agents call it: any time the user says something the agent should remember — corrections (“from now on, X”), preferences (“always Y”), conventions (“we name files Z”). With adapter hooks, this fires automatically; without hooks, the agent should call it explicitly whenever the user uses correction language.
Return shape: { id, statement, scope, type, pinned, decision: "ADD", ... } — plus contextual extras: a scope_hint when an unscoped write landed in a personal scope while a team store is configured, a demoted warning when sensitive content was detected and rerouted to a private scope, a routed note when auto-routing matched a scope’s covers, and an expiry_note when a validity phrase was auto-parsed from the statement.
Gotchas:
- A self-contained
statementis critical. “Always validate input at API boundaries” beats “Do that.” commitmentdefaults toleaning— fine for most. Reservelockedfor safety/operating principles you never want to drift away from.- Setting
pinned: truemakes the engram always-load — see Activation & decay. - Use
supersedeswhen updating a standing fact (new version, changed rule). Supersedes-linked pairs are skipped by tension scans — an intentional update is not a contradiction. See Tensions. - In multi-agent orchestrations, have the parent session own
plur_learnwrites — subagents should return findings as text for the parent to persist.
plur_recall_hybrid
Section titled “plur_recall_hybrid”The default search. BM25 + local embeddings + Reciprocal Rank Fusion. Fully local, zero API cost.
plur_recall_hybrid( query: string, domain?: string, scope?: string, limit?: number, // default 20 include_episodes?: boolean) → RecallResultWhen agents call it: before answering a factual question the user asks, especially anything project-specific. The PLUR convention is “check memory before guessing” — if plur_recall_hybrid returns relevant engrams, cite them; if it returns nothing, fall back to other tools.
Return shape:
{ results: Array<{ id: string, statement: string, type: string, scope: string, domain?: string, retrieval_strength: number, episodes?: Array<{ id, summary, timestamp }> // if include_episodes }>, count: number, truncated: boolean, mode: "hybrid" | "hybrid-degraded"}Gotchas:
mode: "hybrid-degraded"plus awarningmeans the embedding layer failed to load and results are BM25-only — runplur_doctor.- When
PLUR_RERANKERis set, the response includesreranked(how many candidates the cross-encoder re-scored) and areranker_warningif the reranker failed to engage. - For a keyword-only path with no embedder, use
plur_recallinstead.
plur_recall
Section titled “plur_recall”BM25 keyword search. Instant; no embedder needed.
plur_recall(query: string, scope?: string, domain?: string, limit?: number) → RecallResultWhen to choose this over hybrid: latency-sensitive paths, or when you know the exact word the engram uses. Otherwise default to plur_recall_hybrid.
Gotcha: a project-scope filter also returns personal-family engrams (local, global, user:*, agent:*), and an explicit scope=global recall returns ALL personal-family engrams — wider than scope=global inject, which is targeted to the global namespace only.
plur_inject / plur_inject_hybrid
Section titled “plur_inject / plur_inject_hybrid”Load relevant memories for a current task — returns a structured block ready to drop into a system prompt.
plur_inject(task: string, budget?: number, scope?: string) → InjectResultplur_inject_hybrid(task: string, budget?: number, scope?: string) → InjectResultDifference from recall: recall returns results by relevance to a query string. inject returns budget-aware results (default budget ~2000 tokens) formatted as directives / consider blocks suitable for prepending to context. The hook layer calls injection automatically; agents rarely call it themselves.
When agents call it manually: when starting a sub-task within a session that’s substantively different from the session task. Example: mid-conversation pivot from “fix login bug” to “deploy frontend” — call plur_inject_hybrid("deploy frontend") to refresh context.
Gotcha: the result can carry warnings when injected engrams are party to unresolved tensions — contradicted context is flagged, not silently injected. See Tensions.
plur_feedback
Section titled “plur_feedback”Rate an engram. Trains future recall. Supports single and batch mode.
plur_feedback(id: string, signal: "positive" | "negative" | "neutral") → FeedbackResultplur_feedback(signals: Array<{ id, signal }>) → BatchFeedbackResultWhen agents call it: at the end of a turn or session, for engrams that were injected at session start. Positive when the engram visibly helped; negative when it was off-topic or contradicted. Hooks emit implicit feedback automatically; the explicit call is reserved for the unambiguous cases.
Gotchas:
- One feedback signal moves activation a small amount; the loop matters at volume, not per-call.
- Feedback on an engram in a readonly store is noted for the session but not persisted.
plur_pin
Section titled “plur_pin”Toggle the always-load flag on an engram. Pinned engrams bypass the keyword-relevance gate at injection time and are eligible for loading in every session, regardless of overlap with the task. Pass {id, pinned: true} to pin, {id, pinned: false} to unpin, or {list: true} to see the current pinned set without mutating anything. Use sparingly — meta-rules, safety conventions, core operating principles only; a store full of pinned engrams defeats relevance gating entirely.
plur_pin(id?: string, pinned?: boolean, list?: boolean) → PinResultplur_forget
Section titled “plur_forget”Retire an engram. History preserved; excluded from injection.
plur_forget(id?: string, search?: string) → ForgetResultWhen agents call it: when a saved rule was wrong (not just outdated — outdated engrams decay naturally). Pass an exact id, or a search term — if multiple engrams match the search, the tool returns the matches and asks for an exact ID instead of guessing.
plur_promote
Section titled “plur_promote”Promote candidate engrams to active so they appear in injection results.
plur_promote(id?: string, ids?: string[]) → PromoteResultCandidates are engrams created by auto-extraction that need confirmation. Useful in workflows where you ingest a batch (plur_ingest) and then promote selectively after review. Retired engrams cannot be promoted.
plur_report_failure
Section titled “plur_report_failure”Report a failure for a procedural engram — triggers procedure evolution via LLM if one is configured. When following a stored procedure goes wrong, this logs a failure episode against the engram and, if llm_base_url + llm_api_key are provided, asks the LLM to rewrite the procedure based on the failure context (bumping engram_version). Only works on procedural engrams; capped at 3 revisions per procedure per 24h so a flaky environment can’t churn a procedure into noise.
plur_report_failure(engram_id: string, failure_context: string, llm_base_url?, llm_api_key?, llm_model?) → ReportFailureResultplur_similarity_search
Section titled “plur_similarity_search”Search engrams by cosine similarity, returning raw scores. Unlike plur_recall_hybrid (which fuses BM25 and embeddings into a rank), this returns the embedding-only cosine score per result — which is what you want for dedup classification: scores > 0.9 indicate duplicates, 0.7–0.9 related engrams, < 0.7 genuinely new content. Used by dedup pipelines and useful before a bulk import to check what already exists.
plur_similarity_search(query: string, limit?: number, scope?: string) → SimilaritySearchResultEpisodes & history
Section titled “Episodes & history”plur_capture
Section titled “plur_capture”Record an episode (timestamped event).
plur_capture( summary: string, agent?: string, channel?: string, // e.g. "session", "hook", "incident", "mcp" session_id?: string, tags?: string[]) → CaptureResultWhen agents call it: anything you’d put in an event log — deploys, incidents, decisions, agent-emitted milestones. Hooks capture episodes automatically at session end; explicit calls are for application-driven events.
plur_timeline
Section titled “plur_timeline”Query episode history.
plur_timeline( since?: ISO-date, until?: ISO-date, agent?: string, channel?: string, search?: string // full-text search within summaries) → TimelineResultWhen agents call it: post-mortem questions (“what happened in this session before the failure?”), weekly-review questions (“what got captured in the incident channel last week?”). Pair with plur_recall_hybrid for the engrams that correspond to those episodes.
plur_episode_to_engram
Section titled “plur_episode_to_engram”Promote an episode into a persistent engram.
plur_episode_to_engram(episode_id: string, scope?: string, domain?: string, tags?: string[]) → LearnResultWhen agents call it: post-mortem distillation. When an incident timeline reveals a recurring failure mode, promoting the lesson into an engram makes it persist and inject next time.
plur_history
Section titled “plur_history”View the event-sourced history of an engram — creation, updates, feedback, and evolution events. Pass engram_id for one engram’s full audit trail, or omit it for the most recent events across all engrams (default limit 50). This is the provenance view: every material change to an engram is an event, so plur_history answers “when did this statement change, and why?”
plur_history(engram_id?: string, limit?: number) → HistoryResultIngestion
Section titled “Ingestion”plur_ingest
Section titled “plur_ingest”Extract engram candidates from arbitrary text using pattern matching.
plur_ingest( content: string, source?: string, scope?: string, domain?: string, extract_only?: boolean // default false — saves automatically) → IngestResultWhen agents call it: meeting transcripts, ADRs, post-mortem write-ups, Slack threads — anything narrative-shaped that contains engram-worthy lessons.
Gotchas:
- The default saves extracted engrams. Pass
extract_only: trueto review candidates without writing. - Extraction is pattern-based, not an LLM pass — structured, assertion-shaped input extracts far better than rambling prose.
Meta-engrams & profile
Section titled “Meta-engrams & profile”plur_extract_meta
Section titled “plur_extract_meta”Distil cross-domain principles from your engrams via a 6-stage pipeline (structural analysis → clustering → alignment → formulation → hierarchy). Requires an LLM endpoint.
plur_extract_meta( llm_base_url: string, // OpenAI-compatible API base URL llm_api_key: string, llm_model?: string, // default gpt-4o-mini domain?: string, scope?: string, run_validation?: boolean, dry_run?: boolean) → ExtractMetaResultWhen agents call it: rarely directly; usually as part of a scheduled maintenance pass.
plur_meta_engrams
Section titled “plur_meta_engrams”List extracted meta-engrams (engrams with the META- prefix), with structural templates and confidence scores.
plur_meta_engrams(domain?: string, min_confidence?: number, hierarchy_level?: "mop" | "top", limit?: number) → MetaListResultplur_validate_meta
Section titled “plur_validate_meta”Test a meta-engram template against a new domain. Requires an LLM endpoint.
plur_validate_meta(meta_engram_id: string, test_domain: string, llm_base_url: string, llm_api_key: string, llm_model?: string) → ValidateMetaResultReturns whether the prediction held, the alignment score, and the updated composite confidence — useful when porting principles between projects.
plur_profile
Section titled “plur_profile”Generate or retrieve a cognitive profile — a narrative summary synthesized from stored engrams, cached for 24h. Without LLM credentials it returns the cached profile (or a stale one, clearly labelled); with llm_base_url + llm_api_key it regenerates when the cache has expired or force_regenerate: true is passed. Useful as a compact “who is this user / what does this store know” block for adapters that can’t afford full injection.
plur_profile(scope?: string, llm_base_url?, llm_api_key?, llm_model?, force_regenerate?: boolean) → ProfileResultTensions
Section titled “Tensions”See Tensions & contradictions for the full concept.
plur_tensions
Section titled “plur_tensions”The tension lifecycle tool — list, scan, and resolve contradictions between engrams. Default mode lists persisted tension records (unresolved first, filterable by status). scan: true runs an LLM contradiction scan over candidate pairs, persists NEW detections as tension records (T-YYYY-MMDD-NNN), and skips already-recorded pairs; persist: false makes the scan a dry run. Lifecycle actions operate on a record by id: action: "confirm" marks a real conflict, action: "dismiss" marks a false positive and suppresses the pair from future scans, action: "resolve" + winner: <engram_id> keeps the winner and retires the losing engram. Scan mode requires an LLM — OPENAI_API_KEY or OPENROUTER_API_KEY in the environment, or explicit llm_base_url + llm_api_key args; tuning knobs include min_confidence (default 0.7), max_pairs (default 50), batch_size, and temporal_discount.
plur_tensions(scan?, persist?, action?, id?, winner?, status?, scope?, domain?, min_confidence?, max_pairs?, batch_size?, temporal_discount?, llm_base_url?, llm_api_key?, llm_model?) → TensionsResultplur_tensions_purge
Section titled “plur_tensions_purge”Purge all legacy conflict relations from local engrams. Older importer heuristics and the pre-lifecycle tension system wrote unvalidated relations.conflicts edges directly onto engrams; this removes them all in one pass and reports how many references were purged from how many engrams. Use it when plur_tensions list mode shows a pile of legacy_conflicts you’d rather clear than judge. Destructive but narrow — it only touches conflict edges, never statements or persisted tension records.
plur_tensions_purge() → PurgeResultSystem
Section titled “System”plur_status
Section titled “plur_status”Health check.
plur_status() → StatusResultReturns: running version, engram count, episode count, pack count, storage root, locked count, tension count, outbox count, injection-provenance event counts, capability canary status, and an update_available block when a newer @plur-ai/mcp exists.
When agents call it: the user asks “is PLUR working?” or the agent suspects something’s off (tools silently returning empty results).
plur_doctor
Section titled “plur_doctor”Diagnose the PLUR engine — embedder, hybrid search, reranker, and remote-store auth.
plur_doctor(retry?: boolean, rerank_eval?: boolean) → DoctorResultChecks whether the embedding model loaded, whether hybrid search is fully operational, whether a configured reranker is engaging, and — for any configured Enterprise/remote store — whether its auth is valid (probes /api/v1/me and decodes token expiry). Each failed check comes with remediation steps. retry: true resets cached embedder/reranker failure state and retries the model load. rerank_eval: true runs the per-store reranker self-eval gate (advisory — see Benchmarks).
Gotcha: this tool diagnoses the engine, not hook/MCP wiring. For .cursor/mcp.json, hooks config, and live MCP tool counts, run the plur doctor CLI command in a terminal — a different, more thorough check with the same name.
plur_sync
Section titled “plur_sync”Sync engrams across machines via git, and refresh the derived index from YAML.
plur_sync(remote?: string, full?: boolean) → SyncResultFirst call can pass remote to set up cross-device sync; subsequent calls reuse it. full: true drops and rebuilds the derived index from YAML (recovery path — YAML is never modified). Also flushes the outbox of queued remote writes. See Sync.
plur_sync_status
Section titled “plur_sync_status”Check git sync status — whether the repo is initialized, has a remote, is dirty, and its ahead/behind counts. The read-only companion to plur_sync: call it to see whether there is anything to push or pull before paying for a full sync, or to debug “my engrams aren’t on my other machine.”
plur_sync_status() → SyncStatusResultplur_batch_decay
Section titled “plur_batch_decay”Apply ACT-R decay to all local engrams. Run weekly (typically from a scheduled maintenance pass). Unused engrams lose retrieval strength over time; this is the pass that actually applies the decay curve and transitions engrams between statuses (active → dormant). Only decays engrams in the local YAML store — remote-store engrams are not decayed client-side. Pass context_scope to exempt the currently-active scope from decay. Returns the status transitions that occurred.
plur_batch_decay(context_scope?: string) → BatchDecayResultplur_packs_preview
Section titled “plur_packs_preview”plur_packs_preview(source: string) → PacksPreviewResultInspects a pack directory without installing — manifest, engram list, security scan, and warnings. Always preview before installing from an untrusted source.
plur_packs_install
Section titled “plur_packs_install”plur_packs_install(source: string) → PacksInstallResultsource is a path to the pack directory. Runs a mandatory security scan (blocks if secrets are found), detects conflicts with existing engrams, and records install metadata in the registry. Call plur_packs_preview first so the user can review what they’re importing.
plur_packs_list
Section titled “plur_packs_list”plur_packs_list() → PacksListResultLists installed packs with integrity hashes, install dates, and source paths. integrity_ok: false means the pack’s engrams were modified after install.
plur_packs_uninstall
Section titled “plur_packs_uninstall”plur_packs_uninstall(name: string) → PacksUninstallResultRemoves the pack and all its engrams.
plur_packs_export
Section titled “plur_packs_export”plur_packs_export( name: string, description?: string, filter_domain?: string, filter_scope?: string, filter_tags?: string[], filter_type?: EngramType, output_dir?: string, // default ~/plur-packs/<name> creator?: string) → PacksExportResultExports engrams as a shareable thematic pack with privacy scanning and an integrity hash. Private and secret-containing engrams are filtered out automatically. See Building a Knowledge Pack.
plur_packs_discover
Section titled “plur_packs_discover”plur_packs_discover(query?, tags?, category?) → PacksDiscoverResultRegistry discovery is not yet implemented — currently returns an empty list with a note. Install packs from local paths via plur_packs_install.
Stores & scopes (Enterprise)
Section titled “Stores & scopes (Enterprise)”plur_stores_add
Section titled “plur_stores_add”Register an additional engram store — filesystem or remote (PLUR Enterprise).
plur_stores_add( scope: string, // required — e.g. "group:plur/engineering" path?: string, // filesystem store (engrams.yaml) url?: string, // remote store base URL (pair with token) token?: string, // Bearer token (JWT or plur_sk_... API key) shared?: boolean, // remote stores default true readonly?: boolean) → StoresAddResultProvide path OR url, not both. One remote URL can host multiple scopes — call once per team scope you’re authorized for. Returns status: "added" or "already_registered". Personal engrams (global, project:*, user:*) stay local; shared scopes (group:*, org:*) hit the server.
Gotcha: a local store is keyed by its path — registering a new scope on an already-registered path is a no-op for that scope, and the result says so explicitly.
plur_stores_list
Section titled “plur_stores_list”plur_stores_list() → StoresListResultLists configured stores with scope, path, and engram count. Stores that declare self-describing scope metadata include their description and covers topics. Also reports pending outbox writes when there are any.
plur_suggest_scope
Section titled “plur_suggest_scope”Suggest which registered scope(s) an engram belongs in, ranked by fit. Deterministic — no LLM, no network. Scores the statement keywords, optional domain (a dotted namespace like plur.core.security — the strongest routing signal), and tags against the covers[] list each scope declares. Advisory only: it does not route or store anything; pass the chosen scope to plur_learn yourself. Returns candidates sorted by confidence, or an empty list when nothing matches.
plur_suggest_scope(statement: string, domain?: string, tags?: string[]) → SuggestScopeResultplur_scopes_discover
Section titled “plur_scopes_discover”Discover which scopes your remote token is authorized for via the Enterprise server (GET /api/v1/me), and which of those aren’t yet registered locally. Read-only by default; pass register: true to register all authorized-but-unregistered scopes in one step. Only shared-family scopes (group:/project:/space:/team:/org:/public) are auto-registered — personal-family scopes advertised by /me are skipped and surfaced in the result. Use this when you have access to multiple team scopes on one server.
plur_scopes_discover(url?: string, register?: boolean) → ScopesDiscoverResultTool profiles
Section titled “Tool profiles”The server exposes one of two profiles, selected by the PLUR_TOOL_PROFILE environment variable:
| Profile | Tools | Used by |
|---|---|---|
full (default) | All 39 regular tools | Claude Code, OpenClaw, Hermes, custom adapters |
cursor | 10 core tools + plur_admin | Cursor (installed by plur init --cursor) |
Cursor caps a workspace at roughly 40 MCP tools total across every server, and PLUR’s full surface alone would consume nearly all of it. The reduced profile keeps the day-to-day tools as first-class: plur_session_start, plur_session_end, plur_learn, plur_recall_hybrid, plur_feedback, plur_forget, plur_status, plur_doctor, plus the destructive tools plur_packs_uninstall and plur_tensions_purge (kept out of the dispatch so clients can still see their risk annotations).
plur_admin
Section titled “plur_admin”Dispatch for the less-common PLUR operations in the Cursor profile — packs, sync, tensions, stores, timeline, ingest, and the rest. Set action to the underlying tool name and args to that tool’s normal arguments; the dispatched call is validated against the target tool’s schema exactly as a direct call would be, and errors name the dispatched action.
plur_admin(action: string, args?: object) → <target tool's result>In the full profile, plur_admin is not registered — every tool is available directly.
Conventions
Section titled “Conventions”Error responses
Section titled “Error responses”Invalid arguments return a structured error rather than a transport failure:
{ error: string, success: false, received_fields: string[] }The error message names the offending field(s) and states explicitly that the call reached the server — agents should fix the arguments and retry, not abandon the call. Some MCP clients drop the entire arguments payload when an array-typed parameter is included (plur-ai/plur#297); the server detects this case and suggests passing arrays as JSON strings or comma-separated strings, both of which it coerces back into arrays.
Permissions (Enterprise only)
Section titled “Permissions (Enterprise only)”When talking to a PLUR Enterprise server, scope permissions are enforced server-side — the token’s resolved scope set is checked against the requested operation. Local @plur-ai/mcp has no permission layer (single user); the tool surface is identical so agents work the same in both modes.
Tool name stability
Section titled “Tool name stability”Do not rename these tools. Agents trained on PLUR — including the engram store itself, which references tool names in stored engrams — expect exact names. If you’re building a custom adapter, preserve the names verbatim.