@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.
- package/README.md +8 -3
- package/dist/index.js +115 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
# agent-memory-mcp
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Codify how you work. Every AI tool obeys.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/xultrax-web/agent-memory-mcp/actions/workflows/ci.yml)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://nodejs.org)
|
|
8
8
|
[](https://modelcontextprotocol.io)
|
|
9
9
|
|
|
10
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.11.7",
|
|
4
4
|
"mcpName": "io.github.xultrax-web/agent-memory-mcp",
|
|
5
|
-
"description": "
|
|
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",
|