@xultrax-web/agent-memory-mcp 0.11.5 → 0.11.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +8 -3
  2. package/dist/index.js +115 -7
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,15 +1,20 @@
1
1
  # agent-memory-mcp
2
2
 
3
- > Markdown memory for AI agents. Your data is just files.
3
+ > Codify how you work. Every AI tool obeys.
4
4
 
5
5
  [![CI](https://github.com/xultrax-web/agent-memory-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/xultrax-web/agent-memory-mcp/actions/workflows/ci.yml)
6
6
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
7
  [![Node](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org)
8
8
  [![MCP](https://img.shields.io/badge/MCP-server-blueviolet)](https://modelcontextprotocol.io)
9
9
 
10
- **Markdown memory for AI agents.** Plain files in a directory you control read them, edit them, grep them, commit them. Operator-grade storage primitives (atomic writes, file locking, soft-delete to `.trash/`, schema versioning, doctor command) wrap the files so nothing rots in the long tail.
10
+ **Memory as constraint, not just recall.** Plain markdown files in a directory you control. Capture your rules + recipes + decisions + context once, applied everywhere across sessions, across machines, across every AI tool you use.
11
11
 
12
- You can `cat` your memory. You can `grep` it. You can edit it in vim. You can commit it to git. You can move it between machines with `scp` or with the built-in `agent-memory sync` (git-backed). If the AI gets a memory wrong, you fix it in a text editor and save. No migration scripts. No vendor lock-in.
12
+ The wedge:
13
+
14
+ 1. **Rules are first-class memories.** Tag with severity (hard / soft), scope, enforce_on category, regex patterns, last_verified date.
15
+ 2. **Companion files emit automatically** to `AGENTS.md` (Linux-Foundation universal standard), `CLAUDE.md` (Claude Code's 5-level hierarchy), `.cursor/rules/*.mdc` (Cursor MDC), and `.gemini/instructions.md` — your rules show up in every tool, every session, with no plugin needed.
16
+ 3. **`check_action` gates destructive operations.** Agent proposes an action, server matches against your rule store, and either issues a [Compliance Receipt](docs/compliance-receipt-protocol-1.0.md) (HMAC-signed bearer token bound to your rules) or returns a structured rejection naming the rule that blocked.
17
+ 4. **Plain files all the way down.** You can `cat` your memory, `grep` it, edit it in vim, commit it to git, sync it to another machine via the built-in `agent-memory sync`. If the AI gets it wrong, you fix it in a text editor and save. No migration scripts. No vendor lock-in. Reference implementation of the [Compliance Receipt Protocol 1.0](docs/compliance-receipt-protocol-1.0.md) — other MCP servers can adopt the same receipts and interoperate.
13
18
 
14
19
  ---
15
20
 
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@
23
23
  */
24
24
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
25
25
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
26
- import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
26
+ import { CallToolRequestSchema, CreateMessageResultSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
27
27
  import Fuse from "fuse.js";
28
28
  import matter from "gray-matter";
29
29
  import { spawnSync } from "node:child_process";
@@ -1504,15 +1504,116 @@ export function checkActionAgainstRules(action, actionType) {
1504
1504
  }
1505
1505
  return { hard, soft, rules_evaluated: rules.length };
1506
1506
  }
1507
- function toolCheckAction(args) {
1507
+ /**
1508
+ * Tier-2 Sampling enrichment · runs ONE rule's natural-language
1509
+ * applies_when conditions past an LLM via MCP sampling/createMessage.
1510
+ * The server makes the request; the client decides (per MCP spec)
1511
+ * whether to forward to its LLM, prompt the user, or refuse.
1512
+ *
1513
+ * On any error (client lacks sampling, user refused, unparseable
1514
+ * response), returns null — Tier-2 silently degrades to "no extra
1515
+ * violations found" and we ship the Tier-1 result.
1516
+ */
1517
+ function clientSupportsSampling() {
1518
+ // server.getClientCapabilities() is undefined before the MCP initialize
1519
+ // handshake; once initialized, returns the capabilities the client
1520
+ // declared. We only call Sampling if `sampling` is in there — saves a
1521
+ // round-trip and prevents test harnesses (which don't respond to
1522
+ // sampling/createMessage) from hanging.
1523
+ try {
1524
+ const caps = server.getClientCapabilities();
1525
+ return !!caps?.sampling;
1526
+ }
1527
+ catch {
1528
+ return false;
1529
+ }
1530
+ }
1531
+ async function runTier2Sampling(rule, action, actionType) {
1532
+ if (!rule.applies_when || rule.applies_when.length === 0)
1533
+ return null;
1534
+ if (!clientSupportsSampling())
1535
+ return null;
1536
+ const prompt = `You are evaluating whether a proposed action violates an operator rule.\n\n` +
1537
+ `RULE:\n` +
1538
+ ` name: ${rule.name}\n` +
1539
+ ` description: ${rule.description}\n` +
1540
+ ` severity: ${rule.severity ?? "soft"}\n` +
1541
+ ` applies_when:\n` +
1542
+ rule.applies_when.map((s) => ` - ${s}`).join("\n") +
1543
+ `\n\nPROPOSED ACTION:\n` +
1544
+ ` ${action}\n` +
1545
+ ` (category: ${actionType})\n\n` +
1546
+ `Does the proposed action match any of the "applies_when" conditions?\n` +
1547
+ `Respond with strict JSON only, no commentary: {"violates": true|false, "reason": "..."}.\n` +
1548
+ `If the action is ambiguous, answer false.`;
1549
+ try {
1550
+ const result = await server.request({
1551
+ method: "sampling/createMessage",
1552
+ params: {
1553
+ messages: [{ role: "user", content: { type: "text", text: prompt } }],
1554
+ systemPrompt: "You are a strict policy evaluator. Reply with JSON only.",
1555
+ maxTokens: 200,
1556
+ modelPreferences: { intelligencePriority: 0.8, speedPriority: 0.4 },
1557
+ },
1558
+ }, CreateMessageResultSchema);
1559
+ const text = result.content.type === "text" ? result.content.text : "";
1560
+ // Tolerate a stray code-fence around the JSON.
1561
+ const cleaned = text.trim().replace(/^```(?:json)?\s*|\s*```$/g, "");
1562
+ const parsed = JSON.parse(cleaned);
1563
+ if (parsed.violates === true) {
1564
+ return {
1565
+ rule: rule.name,
1566
+ severity: rule.severity ?? "soft",
1567
+ reason: `Sampling judgment: ${parsed.reason ?? "applies_when matched"}`,
1568
+ };
1569
+ }
1570
+ return null;
1571
+ }
1572
+ catch (err) {
1573
+ // Sampling unsupported on this client, user refused, response
1574
+ // unparseable, or any other transport-level failure. Degrade
1575
+ // silently to Tier-1 only · we never block a check_action call
1576
+ // because Tier-2 couldn't run.
1577
+ log("debug", "tier2_sampling_skipped", {
1578
+ rule: rule.name,
1579
+ error: err instanceof Error ? err.message : String(err),
1580
+ });
1581
+ return null;
1582
+ }
1583
+ }
1584
+ async function toolCheckAction(args) {
1508
1585
  const action = String(args.action ?? "").trim();
1509
1586
  const actionType = String(args.action_type ?? "").trim();
1510
1587
  const sessionId = typeof args.session_id === "string" ? args.session_id.trim() : "";
1588
+ // Tier-2 Sampling is opt-out: defaults to true on clients that support
1589
+ // it; gracefully degrades on clients that don't. Set to false to skip
1590
+ // the LLM round-trip entirely (e.g. for batched/script use).
1591
+ const tier2Enabled = args.use_sampling !== false;
1511
1592
  if (!action)
1512
1593
  throw new Error("action is required (the proposed action description)");
1513
1594
  if (!actionType)
1514
1595
  throw new Error("action_type is required (e.g. 'deletions', 'commits', 'file_writes', 'chat_responses')");
1515
1596
  const { hard, soft, rules_evaluated } = checkActionAgainstRules(action, actionType);
1597
+ // Tier-2: run Sampling for any rule with applies_when that DIDN'T
1598
+ // already match deterministically. Rules already flagged in Tier-1
1599
+ // don't need a Sampling round-trip (we know they violate).
1600
+ if (tier2Enabled) {
1601
+ const tier1HitRules = new Set([...hard.map((v) => v.rule), ...soft.map((v) => v.rule)]);
1602
+ const rules = loadAllRules();
1603
+ const tier2Candidates = rules.filter((r) => r.applies_when &&
1604
+ r.applies_when.length > 0 &&
1605
+ !tier1HitRules.has(r.name) &&
1606
+ (!r.enforce_on || r.enforce_on.length === 0 || r.enforce_on.includes(actionType)));
1607
+ for (const rule of tier2Candidates) {
1608
+ const violation = await runTier2Sampling(rule, action, actionType);
1609
+ if (violation) {
1610
+ if (violation.severity === "hard")
1611
+ hard.push(violation);
1612
+ else
1613
+ soft.push(violation);
1614
+ }
1615
+ }
1616
+ }
1516
1617
  if (hard.length > 0) {
1517
1618
  const result = {
1518
1619
  approved: false,
@@ -2087,7 +2188,7 @@ function actionColor(action) {
2087
2188
  // -------------------------------------------------------------
2088
2189
  // Server wiring
2089
2190
  // -------------------------------------------------------------
2090
- const server = new Server({ name: "agent-memory", version: "0.11.4" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
2191
+ const server = new Server({ name: "agent-memory", version: "0.11.7" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
2091
2192
  // -------------------------------------------------------------
2092
2193
  // Resource URI scheme
2093
2194
  // -------------------------------------------------------------
@@ -2611,7 +2712,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
2611
2712
  " - APPROVES: returns a short-lived Compliance Receipt (HMAC-signed, 60s default) the agent can pass to destructive tools (e.g. delete_memory) as proof of compliance.\n" +
2612
2713
  " - DENIES: returns structured hard_violations (severity:hard rules that block) and/or soft_warnings (severity:soft rules that warn but allow).\n\n" +
2613
2714
  "Tier 1 (deterministic) matches the action against rule.matches regexes + rule.enforce_on category filter. Works on every MCP client.\n" +
2614
- "Tier 2 (Sampling-enriched LLM judgment on rule.applies_when) ships in v0.11.3.x for clients that support Sampling (Claude Desktop, VS Code Copilot).",
2715
+ "Tier 2 (v0.11.7+) calls back to the client via MCP sampling/createMessage to judge rule.applies_when natural-language conditions. Auto-enabled on clients that declared the sampling capability; silently skipped on clients that didn't.",
2615
2716
  inputSchema: {
2616
2717
  type: "object",
2617
2718
  properties: {
@@ -2627,6 +2728,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
2627
2728
  type: "string",
2628
2729
  description: "Optional session identifier · binds the issued receipt to this session via a caveat.",
2629
2730
  },
2731
+ use_sampling: {
2732
+ type: "boolean",
2733
+ description: "Opt out of Tier-2 Sampling enrichment (default true). Set false for batched/scripted use where the Sampling round-trip would add latency. CLI invocations default this to false automatically.",
2734
+ },
2630
2735
  },
2631
2736
  required: ["action", "action_type"],
2632
2737
  },
@@ -2721,7 +2826,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2721
2826
  result = toolEmitCompanions(args);
2722
2827
  break;
2723
2828
  case "check_action":
2724
- result = toolCheckAction(args);
2829
+ result = await toolCheckAction(args);
2725
2830
  break;
2726
2831
  case "audit":
2727
2832
  result = toolAudit(args);
@@ -3018,11 +3123,14 @@ async function cliMain(command, rest) {
3018
3123
  if (!action || !actionType) {
3019
3124
  throw new Error("Usage: agent-memory check-action '<action description>' --type <action_type> [--session <id>]");
3020
3125
  }
3021
- process.stdout.write(toolCheckAction({
3126
+ process.stdout.write((await toolCheckAction({
3022
3127
  action,
3023
3128
  action_type: actionType,
3024
3129
  session_id: flags.session ? String(flags.session) : undefined,
3025
- }) + "\n");
3130
+ // CLI invocations don't have a Sampling-capable client attached,
3131
+ // so skip Tier 2 to avoid a timeout · keeps the CLI fast.
3132
+ use_sampling: false,
3133
+ })) + "\n");
3026
3134
  return 0;
3027
3135
  }
3028
3136
  case "audit": {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@xultrax-web/agent-memory-mcp",
3
- "version": "0.11.5",
3
+ "version": "0.11.7",
4
4
  "mcpName": "io.github.xultrax-web/agent-memory-mcp",
5
- "description": "Markdown memory for AI agents. Plain files you can read, edit, grep, and commit. Operator-grade storage with atomic writes, file locking, tags, [[wiki-links]], find_related, git-backed multi-machine sync, and an Ink-based TUI.",
5
+ "description": "Codify how you work. Every AI tool obeys. Markdown rules + cross-tool companion files (AGENTS.md/CLAUDE.md/.cursor/rules/.gemini) + Compliance Receipts for protocol-level enforcement of destructive ops. Reference implementation of CRP 1.0. Works on every MCP client (no Sampling required).",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "agent-memory-mcp": "dist/index.js",