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.16.0
to
0.16.1
dist/chunk-27IMBG4G.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-27IMBG4G.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
};
+2
-2

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

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

@@ -355,3 +355,3 @@

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

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

@@ -5,3 +5,3 @@ import {

validateToolArgs
} from "./chunk-TPL7QPPO.js";
} from "./chunk-27IMBG4G.js";

@@ -8,0 +8,0 @@ // src/tools-export.ts

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

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

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

@@ -22,0 +22,0 @@ "devDependencies": {

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

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
};