Skip to content

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: sessionmemoryepisodes & historyingestionmetatensionssystempacksstores & scopesprofiles.

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) → SessionStartResult

When 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_start twice in the same conversation is harmless but produces a new session ID — episodes can get split across IDs. Prefer one start per task.
  • default_scope sets the session-wide fallback scope for plur_learn. Usually leave it unset and pass scope per engram instead — one session’s learnings rarely all belong in one store.

End a session, capture summary and learnings.

plur_session_end(
summary: string,
engram_suggestions: Array<{ statement, type? } | string>,
session_id?: string
) → SessionEndResult

When 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, and total_engrams — plus a hint if zero engrams were captured.

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
) → LearnResult

When 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 statement is critical. “Always validate input at API boundaries” beats “Do that.”
  • commitment defaults to leaning — fine for most. Reserve locked for safety/operating principles you never want to drift away from.
  • Setting pinned: true makes the engram always-load — see Activation & decay.
  • Use supersedes when 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_learn writes — subagents should return findings as text for the parent to persist.

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
) → RecallResult

When 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 a warning means the embedding layer failed to load and results are BM25-only — run plur_doctor.
  • When PLUR_RERANKER is set, the response includes reranked (how many candidates the cross-encoder re-scored) and a reranker_warning if the reranker failed to engage.
  • For a keyword-only path with no embedder, use plur_recall instead.

BM25 keyword search. Instant; no embedder needed.

plur_recall(query: string, scope?: string, domain?: string, limit?: number) → RecallResult

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

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) → InjectResult
plur_inject_hybrid(task: string, budget?: number, scope?: string) → InjectResult

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

Rate an engram. Trains future recall. Supports single and batch mode.

plur_feedback(id: string, signal: "positive" | "negative" | "neutral") → FeedbackResult
plur_feedback(signals: Array<{ id, signal }>) → BatchFeedbackResult

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

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) → PinResult

Retire an engram. History preserved; excluded from injection.

plur_forget(id?: string, search?: string) → ForgetResult

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

Promote candidate engrams to active so they appear in injection results.

plur_promote(id?: string, ids?: string[]) → PromoteResult

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

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?) → ReportFailureResult

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) → SimilaritySearchResult

Record an episode (timestamped event).

plur_capture(
summary: string,
agent?: string,
channel?: string, // e.g. "session", "hook", "incident", "mcp"
session_id?: string,
tags?: string[]
) → CaptureResult

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

Query episode history.

plur_timeline(
since?: ISO-date,
until?: ISO-date,
agent?: string,
channel?: string,
search?: string // full-text search within summaries
) → TimelineResult

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

Promote an episode into a persistent engram.

plur_episode_to_engram(episode_id: string, scope?: string, domain?: string, tags?: string[]) → LearnResult

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

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) → HistoryResult

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
) → IngestResult

When 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: true to review candidates without writing.
  • Extraction is pattern-based, not an LLM pass — structured, assertion-shaped input extracts far better than rambling prose.

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
) → ExtractMetaResult

When agents call it: rarely directly; usually as part of a scheduled maintenance pass.

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) → MetaListResult

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) → ValidateMetaResult

Returns whether the prediction held, the alignment score, and the updated composite confidence — useful when porting principles between projects.

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) → ProfileResult

See Tensions & contradictions for the full concept.

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?) → TensionsResult

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() → PurgeResult

Health check.

plur_status() → StatusResult

Returns: 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).

Diagnose the PLUR engine — embedder, hybrid search, reranker, and remote-store auth.

plur_doctor(retry?: boolean, rerank_eval?: boolean) → DoctorResult

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

Sync engrams across machines via git, and refresh the derived index from YAML.

plur_sync(remote?: string, full?: boolean) → SyncResult

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

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() → SyncStatusResult

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 (activedormant). 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) → BatchDecayResult
plur_packs_preview(source: string) → PacksPreviewResult

Inspects a pack directory without installing — manifest, engram list, security scan, and warnings. Always preview before installing from an untrusted source.

plur_packs_install(source: string) → PacksInstallResult

source 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() → PacksListResult

Lists 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(name: string) → PacksUninstallResult

Removes the pack and all its engrams.

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
) → PacksExportResult

Exports 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(query?, tags?, category?) → PacksDiscoverResult

Registry discovery is not yet implemented — currently returns an empty list with a note. Install packs from local paths via plur_packs_install.

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
) → StoresAddResult

Provide 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() → StoresListResult

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

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[]) → SuggestScopeResult

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) → ScopesDiscoverResult

The server exposes one of two profiles, selected by the PLUR_TOOL_PROFILE environment variable:

ProfileToolsUsed by
full (default)All 39 regular toolsClaude Code, OpenClaw, Hermes, custom adapters
cursor10 core tools + plur_adminCursor (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).

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.

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.

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.

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.