New:Socket for Asana Is Now Available.Learn more
Sign In

@plur-ai/mcp

Package Overview
Dependencies
Maintainers
1
Versions
59
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@plur-ai/mcp - npm Package Compare versions

Comparing version
0.15.0
to
0.16.0
dist/chunk-TPL7QPPO.js

Sorry, the diff of this file is too big to display

+378
import {
CURSOR_CORE_TOOL_NAMES,
VERSION,
getToolDefinitions,
mcpCanary,
registerFlushOnExit,
validateToolArgs
} from "./chunk-TPL7QPPO.js";
// src/server.ts
import { Server, ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import { existsSync, readFileSync, writeFileSync } from "fs";
import { join } from "path";
import { homedir } from "os";
import { Plur, checkForUpdate } from "@plur-ai/core";
function serverPidPath(baseDir) {
return join(baseDir ?? join(homedir(), ".plur"), "server.pid");
}
function readEnterpriseToken(baseDir) {
const configPath = join(baseDir ?? join(homedir(), ".plur"), "config.json");
if (!existsSync(configPath)) return void 0;
try {
const cfg = JSON.parse(readFileSync(configPath, "utf8"));
const ent = cfg?.enterprise;
if (!ent || typeof ent.url !== "string" || typeof ent.token !== "string") return void 0;
return { url: ent.url, token: ent.token, username: ent.username };
} catch {
return void 0;
}
}
var _pendingReload = false;
function isPendingReload() {
return _pendingReload;
}
function clearPendingReload() {
_pendingReload = false;
}
var INSTRUCTIONS = `PLUR is your persistent memory. Corrections, preferences, and conventions persist across sessions as engrams.
PLUR is a GLOBAL tool \u2014 one MCP server, one engram store (~/.plur/), available in every project. Multi-project scoping uses domain/scope fields on engrams, not separate installations.
TOOL PROFILE: by default only the core session tools are exposed directly (lean profile). Every other plur_* operation is reachable via plur_admin: { action: "<tool name>", args: {...} } \u2014 same arguments and validation as a direct call. PLUR_TOOL_PROFILE=full exposes everything directly.
SESSION LIFECYCLE:
- With hooks installed (plur init): engrams are injected automatically on first message. You do NOT need to call plur_session_start \u2014 it happens via hooks. Just call plur_session_end before the conversation ends.
- Without hooks: call plur_session_start at the start, plur_session_end at the end.
DURING the session:
- When user corrects you ("no, use X not Y") \u2192 call plur_learn immediately
- When user states a preference ("always X", "never Y") \u2192 call plur_learn immediately
- When you discover a codebase convention or pattern \u2192 call plur_learn
SCOPE SELECTION (set scope PER engram, by content \u2014 not once per session):
A single session produces engrams that belong in different stores. Choose the
"scope" on EACH plur_learn call from what the engram is about:
- Team / engineering / architecture / shared-convention knowledge \u2192 the matching
team store scope (e.g. "group:<org>/<team>"). plur_session_start lists the
scopes this install can write to \u2014 use them.
- Personal preferences, your workflow, or details specific to one project \u2192 the
default/local scope (or "project:<name>").
- "global" is for genuinely cross-project facts only (language gotchas, tool
quirks). Do NOT let team-relevant knowledge fall back to "global" by omitting
scope \u2014 with a team store configured it never reaches the team store. If no
scope fits, prefer the project/local default over global.
OPTIONAL but improves quality:
- Call plur_feedback to rate which injected engrams helped (positive/negative)
- Call plur_recall before answering factual questions \u2014 the answer may be in memory
Do not ask permission to use these tools \u2014 they are your memory system.
Setup: If this is a fresh install, suggest the user run: npx @plur-ai/mcp init
This installs hooks for automatic injection + session management. One-time global setup.`;
var GUIDE_RESOURCE = `# PLUR \u2014 Agent Guide
## What is PLUR?
Persistent memory for AI agents. Corrections, preferences, and conventions are stored as **engrams** \u2014 small assertions that strengthen with use and decay when irrelevant (ACT-R model). Storage is plain YAML on disk. Search is fully local (BM25 + embeddings). Zero API calls.
## Quick Start
1. \`plur_session_start\` \u2014 start a session, inject relevant context
2. \`plur_learn\` \u2014 store a new learning
3. \`plur_feedback\` \u2014 rate injected engrams
4. \`plur_session_end\` \u2014 capture summary and new learnings
## When to Call Each Tool
| Trigger | Tool |
|---------|------|
| Session starts | \`plur_session_start\` with task description |
| User corrects you | \`plur_learn\` with the correction |
| User states preference ("always X", "never Y") | \`plur_learn\` with scope and type |
| You used a recalled engram successfully | \`plur_feedback\` with "positive" |
| A recalled engram was wrong or irrelevant | \`plur_feedback\` with "negative" |
| User says "forget X" or a memory is outdated | \`plur_forget\` |
| You need to check what's stored | \`plur_status\` or \`plur_packs_list\` |
| User asks what memory did for them / is memory working | \`plur_receipt\` (relay its \`summary\`; activation_rate is coverage, not quality) |
| End of session | \`plur_session_end\` with summary and suggestions |
## Tool Categories
### Session Management
- **plur_session_start** \u2014 start a session, inject relevant context
- **plur_session_end** \u2014 end a session, capture summary and new learnings
### Core Memory
- **plur_learn** \u2014 store a correction, preference, or convention
- **plur_recall** \u2014 hybrid search by default (BM25 + embeddings); pass mode:"keyword" for BM25-only
- **plur_feedback** \u2014 rate an engram (trains relevance)
- **plur_forget** \u2014 retire an outdated engram
- **plur_promote** \u2014 activate a candidate engram
### Context Injection
- **plur_inject** \u2014 select engrams for a task (BM25)
- **plur_inject_hybrid** \u2014 select engrams for a task (BM25 + embeddings, recommended)
### Episodic Timeline
- **plur_capture** \u2014 record what happened in a session
- **plur_timeline** \u2014 query past episodes
### Knowledge Management
- **plur_ingest** \u2014 extract engrams from text content
- **plur_packs_install** \u2014 install curated engram packs
- **plur_packs_list** \u2014 list installed packs
- **plur_packs_export** \u2014 export engrams as a shareable pack
### Multi-Store
- **plur_stores_add** \u2014 register an additional engram store
- **plur_stores_list** \u2014 list all configured stores
**Note:** Multi-store is currently config-only. Recall and inject search the primary store. Cross-store search coming in a future release.
### Sync & Status
- **plur_sync** \u2014 sync engrams across devices via git
- **plur_sync_status** \u2014 check sync state
- **plur_status** \u2014 system health
- **plur_receipt** \u2014 counted report of what memory retrieved for the user (local, read-only)
## Scoping
Use \`scope\` to namespace engrams per project:
- \`scope: "global"\` \u2014 applies everywhere (default)
- \`scope: "project:my-app"\` \u2014 applies only to my-app
- Scoped recall automatically includes global engrams
## Storage
\`\`\`
~/.plur/
\u251C\u2500\u2500 engrams.yaml # learned knowledge
\u251C\u2500\u2500 episodes.yaml # session timeline
\u2514\u2500\u2500 config.yaml # settings
\`\`\`
Override with \`PLUR_PATH\` environment variable.
`;
async function createServer(plur, options) {
const instance = plur ?? new Plur();
const tools = getToolDefinitions(options?.profile ?? "lean");
checkForUpdate("@plur-ai/mcp", VERSION, (r) => {
if (r.updateAvailable) {
console.error(`[plur] Update available: ${r.current} \u2192 ${r.latest}. Run: npx @plur-ai/mcp@latest`);
}
});
const server = new Server(
{ name: "plur-mcp", version: VERSION },
{
capabilities: {
tools: {},
resources: {},
prompts: {},
logging: {}
},
instructions: INSTRUCTIONS
}
);
server.setRequestHandler("tools/list", async () => ({
tools: tools.map((t) => ({
name: t.name,
description: t.description,
inputSchema: t.inputSchema,
...t.annotations && { annotations: t.annotations }
}))
}));
server.setRequestHandler("tools/call", async (request) => {
const tool = tools.find((t) => t.name === request.params.name);
if (!tool) {
const hidden = getToolDefinitions("full").find((t) => t.name === request.params.name);
if (hidden) {
return {
content: [{ type: "text", text: JSON.stringify({
error: `Tool "${request.params.name}" exists but is not directly callable under the current tool profile.`,
success: false,
hint: `Call it via plur_admin: { action: "${request.params.name}", args: { ... } } \u2014 same arguments, same validation, same result. To expose all tools directly, set PLUR_TOOL_PROFILE=full.`
}) }],
isError: true
};
}
return {
content: [{ type: "text", text: JSON.stringify({ error: `Unknown tool: ${request.params.name}`, success: false }) }],
isError: true
};
}
mcpCanary.tick();
try {
let args = request.params.arguments ?? {};
const validated = validateToolArgs(tool, args);
if (!validated.ok) {
return {
content: [{ type: "text", text: JSON.stringify(validated.errorPayload) }],
isError: true
};
}
args = validated.data;
const result = await tool.handler(args, instance);
let payload = result;
let resultIsError = false;
if (result && typeof result === "object" && result._isError === true) {
resultIsError = true;
const { _isError, ...rest } = result;
payload = rest;
}
return {
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
...resultIsError ? { isError: true } : {}
};
} catch (err) {
const message = err?.message ?? String(err);
server.sendLoggingMessage({ level: "error", data: `Tool ${request.params.name} failed: ${message}` });
return {
content: [{ type: "text", text: JSON.stringify({ error: message, success: false }) }],
isError: true
};
}
});
server.setRequestHandler("resources/list", async () => ({
resources: [
{
uri: "plur://guide",
name: "PLUR Agent Guide",
description: "Complete reference for all PLUR tools, when to use them, scoping, and storage",
mimeType: "text/markdown"
},
{
uri: "plur://status",
name: "PLUR Status",
description: "Live system health \u2014 engram count, episode count, pack count, storage path",
mimeType: "application/json"
}
]
}));
server.setRequestHandler("resources/read", async (request) => {
const uri = request.params.uri;
if (uri === "plur://guide") {
const cursorNote = options?.profile === "cursor" || options?.profile === "lean" || options?.profile == null ? `
## Lean tool profile (default)
Most tools above are NOT directly callable in this session \u2014 only ${[...CURSOR_CORE_TOOL_NAMES].join(", ")} are top-level tools here. Everything else in this guide is reachable through **plur_admin**: call it with \`{ action: "<tool name above>", args: {...} }\`. Set \`PLUR_TOOL_PROFILE=full\` to expose all ${getToolDefinitions("full").length} tools directly.` : "";
return {
contents: [{
uri: "plur://guide",
mimeType: "text/markdown",
text: GUIDE_RESOURCE + cursorNote
}]
};
}
if (uri === "plur://status") {
const status = await instance.status();
return {
contents: [{
uri: "plur://status",
mimeType: "application/json",
text: JSON.stringify({
engram_count: status.engram_count,
episode_count: status.episode_count,
pack_count: status.pack_count,
storage_root: status.storage_root,
version: VERSION
}, null, 2)
}]
};
}
throw new ProtocolError(ProtocolErrorCode.InvalidRequest, `Unknown resource: ${uri}`);
});
server.setRequestHandler("prompts/list", async () => ({
prompts: [
{
name: "plur-getting-started",
description: "Step-by-step guide to set up and start using PLUR memory"
},
{
name: "plur-session-start",
description: "Load relevant context for a task \u2014 call at the start of each session",
arguments: [
{ name: "task", description: "Brief description of the task or goal", required: true },
{ name: "scope", description: "Project scope (e.g. project:my-app)", required: false }
]
}
]
}));
server.setRequestHandler("prompts/get", async (request) => {
const name = request.params.name;
if (name === "plur-getting-started") {
const status = await instance.status();
return {
description: "Get started with PLUR memory",
messages: [{
role: "user",
content: {
type: "text",
text: `I just set up PLUR. Here's my current status:
- Engrams stored: ${status.engram_count}
- Episodes recorded: ${status.episode_count}
- Packs installed: ${status.pack_count}
- Storage: ${status.storage_root}
${status.engram_count === 0 ? `I have no memories yet. Help me get started by:
1. Teaching me a coding preference or convention (I'll use plur_learn)
2. Then recalling it to verify it works (I'll use plur_recall)
3. Rating the recall quality (I'll use plur_feedback)` : `I have ${status.engram_count} engrams stored. Try asking me something related to your project \u2014 I'll check my memory first.`}`
}
}]
};
}
if (name === "plur-session-start") {
const task = request.params.arguments?.task ?? "general work";
const scope = request.params.arguments?.scope;
return {
description: "Load relevant context for this session",
messages: [{
role: "user",
content: {
type: "text",
text: `Starting a new session. Task: ${task}${scope ? ` (scope: ${scope})` : ""}
Please:
1. Call plur_recall with query "${task}"${scope ? ` and scope "${scope}"` : ""} to load relevant memories
2. Review the recalled engrams and apply any relevant conventions or preferences
3. If any recalled engrams are helpful, call plur_feedback with "positive"
4. If any are irrelevant, call plur_feedback with "negative"`
}
}]
};
}
throw new ProtocolError(ProtocolErrorCode.InvalidRequest, `Unknown prompt: ${name}`);
});
return server;
}
async function runStdio() {
const envProfile = process.env.PLUR_TOOL_PROFILE;
const profile = envProfile === "full" ? "full" : envProfile === "cursor" ? "cursor" : "lean";
const server = await createServer(void 0, { profile });
registerFlushOnExit({});
try {
writeFileSync(serverPidPath(), String(process.pid));
} catch {
}
if (process.platform !== "win32") {
process.on("SIGUSR1", () => {
_pendingReload = true;
});
}
const transport = new StdioServerTransport();
await server.connect(transport);
}
export {
INSTRUCTIONS,
clearPendingReload,
createServer,
isPendingReload,
readEnterpriseToken,
runStdio,
serverPidPath
};
import { Plur } from '@plur-ai/core';
interface ToolAnnotations {
title?: string;
readOnlyHint?: boolean;
destructiveHint?: boolean;
idempotentHint?: boolean;
openWorldHint?: boolean;
}
interface ToolDefinition {
name: string;
description: string;
inputSchema: {
type: 'object';
[key: string]: unknown;
};
annotations?: ToolAnnotations;
handler: (args: Record<string, unknown>, plur: Plur) => Promise<unknown>;
}
/**
* Validate raw tool-call arguments against a ToolDefinition's inputSchema.
* Shared by the top-level CallToolRequestSchema handler (server.ts) and the
* plur_admin dispatch handler below — one validation path AND one error-
* formatting path, not two that can drift. (An earlier draft of this plan
* only shared the Zod validation and left the #297 array-bug hint / isError
* flag duplicated in server.ts alone — audit review caught that plur_admin's
* ~30 dispatched tools would silently lose both. errorPayload below is the
* fix: the full formatted error, including the `_isError` marker server.ts
* checks generically after ANY tool handler returns, not just this one.)
*/
declare function validateToolArgs(tool: ToolDefinition, rawArgs: Record<string, unknown>): {
ok: true;
data: Record<string, unknown>;
} | {
ok: false;
errorPayload: {
error: string;
success: false;
received_fields: string[];
_isError: true;
};
};
type ToolProfile = 'full' | 'lean' | 'cursor';
declare const CURSOR_CORE_TOOL_NAMES: ReadonlySet<string>;
declare function getToolDefinitions(profile?: ToolProfile): ToolDefinition[];
/**
* Side-effect-free subpath export: @plur-ai/mcp/tools
*
* Importable without starting the MCP server or parsing process.argv.
* Use this to access PLUR's tool surface — names, descriptions, input schemas
* — from a consumer that re-exposes or validates the definitions without
* running the server.
*/
/** Tool definition without the runtime handler — plain data, safe to serialize. */
interface ToolSchema {
name: string;
description: string;
inputSchema: {
type: 'object';
[key: string]: unknown;
};
annotations?: ToolAnnotations;
}
/**
* Return tool definitions as plain, handler-free schema objects.
*
* Useful when you need the MCP tool surface — name, description, and input
* schema — without the runtime handlers that require a live Plur instance.
* A downstream consumer that re-exposes a filtered subset can import this
* list and keep its copy in sync without duplicating schemas.
*/
declare function getToolSchemas(profile?: ToolProfile): ToolSchema[];
export { CURSOR_CORE_TOOL_NAMES, type ToolAnnotations, type ToolDefinition, type ToolProfile, type ToolSchema, getToolDefinitions, getToolSchemas, validateToolArgs };
import {
CURSOR_CORE_TOOL_NAMES,
getToolDefinitions,
validateToolArgs
} from "./chunk-TPL7QPPO.js";
// src/tools-export.ts
function getToolSchemas(profile) {
return getToolDefinitions(profile).map(({ name, description, inputSchema, annotations }) => ({
name,
description,
inputSchema,
...annotations !== void 0 ? { annotations } : {}
}));
}
export {
CURSOR_CORE_TOOL_NAMES,
getToolDefinitions,
getToolSchemas,
validateToolArgs
};
+6
-6

@@ -8,3 +8,3 @@ #!/usr/bin/env node

import { homedir, platform } from "os";
var VERSION = "0.15.0";
var VERSION = "0.16.0";
var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents

@@ -109,3 +109,3 @@

2. **Learn**: When corrected or discovering something new, call \`plur_learn\` immediately
3. **Recall**: Before answering factual questions, call \`plur_recall_hybrid\` \u2014 check memory first
3. **Recall**: Before answering factual questions, call \`plur_recall\` \u2014 check memory first
4. **Feedback**: Rate injected engrams with \`plur_feedback\` (positive/negative) \u2014 trains relevance

@@ -238,3 +238,3 @@ 5. **End**: Call \`plur_session_end\` with summary + engram_suggestions \u2014 a SessionEnd hook auto-closes the lifecycle if you forget, but calling it yourself captures higher-quality learnings

try {
plur.installPack(join(bundledPacksDir, entry));
await plur.installPack(join(bundledPacksDir, entry));
newPacks.push(entry);

@@ -245,3 +245,3 @@ } catch {

try {
plur.installPack(join(bundledPacksDir, entry));
await plur.installPack(join(bundledPacksDir, entry));
upgradedPacks.push(

@@ -292,3 +292,3 @@ `${entry} ${installedVersion ?? "unknown"}\u2192${bundledVersion}`

try {
const result = plur.installPack(source);
const result = await plur.installPack(source);
process.stdout.write(`Installed pack '${result.name}' (${result.installed} engrams)

@@ -359,3 +359,3 @@ `);

if (arg === "serve" || arg === void 0) {
const { runStdio } = await import("./server-7JVOIBJV.js");
const { runStdio } = await import("./server-VHSO4XMV.js");
runStdio().catch((err) => {

@@ -362,0 +362,0 @@ console.error("Failed to start PLUR MCP server:", err);

{
"name": "@plur-ai/mcp",
"mcpName": "io.github.plur-ai/plur",
"version": "0.15.0",
"version": "0.16.0",
"type": "module",

@@ -19,3 +19,3 @@ "bin": {

"zod": "^3.23.0",
"@plur-ai/core": "0.15.0"
"@plur-ai/core": "0.16.0"
},

@@ -47,2 +47,6 @@ "devDependencies": {

"import": "./dist/index.js"
},
"./tools": {
"types": "./dist/tools-export.d.ts",
"import": "./dist/tools-export.js"
}

@@ -49,0 +53,0 @@ },

@@ -93,3 +93,3 @@ engrams:

Before answering factual questions about the user's project, codebase,
preferences, infrastructure, or past decisions, call plur_recall_hybrid
preferences, infrastructure, or past decisions, call plur_recall
first. The answer may already be in memory. Do not fabricate or compose

@@ -103,6 +103,6 @@ answers from prior-conversation context — that context is often stale

domain: plur.recall
tags: [recall, search, before-answering, plur_recall_hybrid, confabulation]
tags: [recall, search, before-answering, plur_recall, confabulation]
activation: { retrieval_strength: 0.95, storage_strength: 0.95, frequency: 0, last_accessed: "2026-05-04" }
dual_coding:
example: "User asks 'how do we deploy to production?' — call plur_recall_hybrid('deployment production') before answering. You might find 'deploy requires git push to nightshift server, not GitHub' from a previous session."
example: "User asks 'how do we deploy to production?' — call plur_recall('deployment production') before answering. You might find 'deploy requires git push to nightshift server, not GitHub' from a previous session."
analogy: "Like checking your notes before answering a question in a meeting, instead of guessing."

@@ -109,0 +109,0 @@ pack: effective-memory

@@ -42,3 +42,3 @@ ---

- **Capture** — call `plur_learn` immediately on corrections; detect correction-shaped phrases.
- **Recall** — `plur_recall_hybrid` before factual answers; don't confabulate.
- **Recall** — `plur_recall` before factual answers; don't confabulate.
- **Session lifecycle** — bookend with `plur_session_start` / `plur_session_end`; `plur_feedback` on injected engrams; `plur_timeline` for long-horizon agents.

@@ -45,0 +45,0 @@ - **Verification** — artifact-first; never bulk-mark as done from narrative text.

@@ -49,3 +49,3 @@ # @plur-ai/mcp

By default (lean profile), your agent gets 11 tools. Everything else is reachable through `plur_admin`:
By default (lean profile), your agent gets 12 tools. Everything else is reachable through `plur_admin`:

@@ -56,3 +56,3 @@ | Tool | What it does |

| `plur_learn` | Store a memory — correction, preference, convention, or decision |
| `plur_recall_hybrid` | **Best default** — BM25 + embeddings merged via RRF. Zero cost. |
| `plur_recall` | **Best default** — hybrid (BM25 + embeddings via RRF) by default; pass `mode:"keyword"` for BM25-only. Zero cost. |
| `plur_feedback` | Rate a memory — trains relevance over time |

@@ -63,2 +63,3 @@ | `plur_forget` | Retire a memory (history preserved) |

| `plur_doctor` | Diagnose embedder, hybrid search, and remote-store auth |
| `plur_receipt` | Show why a memory was injected — the evidence behind a recall |
| `plur_packs_uninstall` | Remove an installed pack |

@@ -68,3 +69,3 @@ | `plur_tensions_purge` | Clear stale/resolved tensions |

Less commonly needed tools (`plur_recall`, `plur_inject_hybrid`, `plur_learn_batch`, `plur_ingest`, `plur_sync`, `plur_packs_install`, `plur_packs_list`, `plur_capture`, `plur_timeline`, and more) are all reachable via `plur_admin`. Set `PLUR_TOOL_PROFILE=full` to expose all 40 tools directly.
Less commonly needed tools (`plur_recall_hybrid`, `plur_inject_hybrid`, `plur_learn_batch`, `plur_ingest`, `plur_sync`, `plur_packs_install`, `plur_packs_list`, `plur_capture`, `plur_timeline`, and more) are all reachable via `plur_admin`. Set `PLUR_TOOL_PROFILE=full` to expose all 40 tools directly.

@@ -71,0 +72,0 @@ ## Sync across machines

Sorry, the diff of this file is too big to display