@memoryprocess/mcp 0.0.1 → 0.0.3-beta.2
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 +16 -16
- package/dist/server.mjs +80 -13
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @memoryprocess/mcp
|
|
2
2
|
|
|
3
|
-
MCP server exposing the memoryprocess toolkit to LLM agents
|
|
3
|
+
MCP server exposing the memoryprocess toolkit to LLM agents - the primary
|
|
4
4
|
agentic surface. Stdio transport; the agent's MCP client owns the server
|
|
5
5
|
process, so open process handles die with the session.
|
|
6
6
|
|
|
@@ -28,7 +28,7 @@ MEMP_NO_EVAL=1 # same as --no-eval
|
|
|
28
28
|
## Session model
|
|
29
29
|
|
|
30
30
|
There is no implicit "current process". `proc_open` returns a session handle
|
|
31
|
-
(`p1`, `p2`,
|
|
31
|
+
(`p1`, `p2`, ...); every `mem_*` tool takes it as `session`, so an agent can
|
|
32
32
|
hold several targets at once. `proc_close` releases the handle and any
|
|
33
33
|
watches/freezes bound to it.
|
|
34
34
|
|
|
@@ -37,38 +37,38 @@ watches/freezes bound to it.
|
|
|
37
37
|
| Tool | Description |
|
|
38
38
|
| --------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
39
39
|
| `proc_list` | List running processes `{pid,name}[]` |
|
|
40
|
-
| `proc_open` | Open by `{pid}` or `{name}`
|
|
40
|
+
| `proc_open` | Open by `{pid}` or `{name}` -> `{session, describe}`. `access:"ro"` = read-only |
|
|
41
41
|
| `proc_close` | Close a session |
|
|
42
42
|
| `mem_describe` | `Process.describe()`: arch, modules, writable/exec regions, threads |
|
|
43
|
-
| `mem_read` | Typed read: `i8
|
|
44
|
-
| `mem_write` | Typed write. `dryRun:true`
|
|
45
|
-
| `mem_scan` | CE value scan
|
|
43
|
+
| `mem_read` | Typed read: `i8...i64/u8...u64`, `f32/f64`, `ptr`, `str`, `wstr`, `bytes[:N]` (+`len`) |
|
|
44
|
+
| `mem_write` | Typed write. `dryRun:true` -> `{addr,current,would,bytes}` preview. Journaled |
|
|
45
|
+
| `mem_scan` | CE value scan -> `{hits:[{addr,value}],count,truncated}` (`module`/`start`/`end`/`limit`) |
|
|
46
46
|
| `mem_scan_next` | Refine hits: `exact`/`changed`/`unchanged`/`increased`/`decreased`/`increasedBy`/`decreasedBy`/`between` |
|
|
47
|
-
| `mem_pointer_resolve` | `module`+`rva`+`offsets[]`
|
|
48
|
-
| `mem_watch` | Poll an i32 every `ms`
|
|
47
|
+
| `mem_pointer_resolve` | `module`+`rva`+`offsets[]` -> resolved address (CE pointer syntax) |
|
|
48
|
+
| `mem_watch` | Poll an i32 every `ms` -> `{watchId}`; changes accumulate |
|
|
49
49
|
| `mem_watch_poll` | Drain recorded changes `[{value,ts}]` for a `watchId` |
|
|
50
50
|
| `mem_unwatch` | Stop a watch |
|
|
51
|
-
| `mem_freeze` | Re-write a value every `ms`
|
|
51
|
+
| `mem_freeze` | Re-write a value every `ms` -> `{freezeId}` |
|
|
52
52
|
| `mem_unfreeze` | Stop a freeze |
|
|
53
53
|
| `mem_undo` | Revert last `n` journaled writes (self-correction primitive) |
|
|
54
54
|
| `mem_journal` | Write-log snapshot `[{index,addr,bytes}]` |
|
|
55
|
-
| `eval_js` | Run JS with `proc` (`Process`) in scope
|
|
55
|
+
| `eval_js` | Run JS with `proc` (`Process`) in scope - the escape hatch |
|
|
56
56
|
|
|
57
57
|
## Wire format (docs/json-conventions.md)
|
|
58
58
|
|
|
59
59
|
- Every result is `{ok:true,data}` or `{ok:false,error:{code,message,win32code?}}`
|
|
60
|
-
|
|
61
|
-
(`READ_FAILED`, `PROCESS_NOT_FOUND`,
|
|
60
|
+
- `error.code` is the typed code from `@memoryprocess/core`
|
|
61
|
+
(`READ_FAILED`, `PROCESS_NOT_FOUND`, ...) plus `UNKNOWN_SESSION`,
|
|
62
62
|
`UNKNOWN_WATCH`, `UNKNOWN_FREEZE`, `MODULE_NOT_FOUND`, `INVALID_ARGS`,
|
|
63
63
|
`EVAL_ERROR`.
|
|
64
|
-
- Addresses/pointers/64-bit values are `"0x
|
|
64
|
+
- Addresses/pointers/64-bit values are `"0x..."` hex strings; values that fit
|
|
65
65
|
in a JS number stay numbers; raw bytes are hex strings.
|
|
66
|
-
- Address args accept `"0x
|
|
66
|
+
- Address args accept `"0x..."`, decimal strings, or `"module+rva"`.
|
|
67
67
|
|
|
68
68
|
## Safety
|
|
69
69
|
|
|
70
|
-
All writes are journaled
|
|
70
|
+
All writes are journaled -> `mem_journal` + `mem_undo` let an agent preview
|
|
71
71
|
(`dryRun`), commit, inspect, and revert. `eval_js` runs arbitrary JS on the
|
|
72
|
-
host
|
|
72
|
+
host - it's on by default (agents already have code execution) and can be
|
|
73
73
|
disabled with `--no-eval` / `MEMP_NO_EVAL=1`, which omits it from
|
|
74
74
|
`tools/list` entirely.
|
package/dist/server.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import { MemoryProcessError, Process } from "@memoryprocess/core";
|
|
|
16
16
|
*
|
|
17
17
|
* Wire format follows docs/json-conventions.md:
|
|
18
18
|
* - every result is `{ok:true,data} | {ok:false,error:{code,message,win32code?}}`
|
|
19
|
-
* - bigints (addresses, pointers, 64-bit values) -> '0x
|
|
19
|
+
* - bigints (addresses, pointers, 64-bit values) -> '0x...' strings
|
|
20
20
|
* - Buffers -> lowercase hex strings
|
|
21
21
|
* - errors carry the typed `.code` from @memoryprocess/core when available
|
|
22
22
|
*/
|
|
@@ -57,8 +57,8 @@ function errorEnvelope(e) {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
|
-
* Parse an address argument: `0x
|
|
61
|
-
* `"module+rva"` (resolved against the session's module table
|
|
60
|
+
* Parse an address argument: `0x...` hex string, decimal string, or
|
|
61
|
+
* `"module+rva"` (resolved against the session's module table -- the rva is
|
|
62
62
|
* optional). Returns the absolute address as bigint.
|
|
63
63
|
*/
|
|
64
64
|
function parseAddress(proc, v, name = "addr") {
|
|
@@ -117,9 +117,9 @@ function parseNumeric(v, wide, name = "value") {
|
|
|
117
117
|
}
|
|
118
118
|
/** Agent-facing type aliases -> core WriteValueType/ScanValueType. */
|
|
119
119
|
const NUMERIC_TYPES = {
|
|
120
|
-
i8: "
|
|
120
|
+
i8: "int8",
|
|
121
121
|
u8: "uint8",
|
|
122
|
-
i16: "
|
|
122
|
+
i16: "int16",
|
|
123
123
|
u16: "uint16",
|
|
124
124
|
i32: "int32",
|
|
125
125
|
u32: "uint32",
|
|
@@ -345,7 +345,7 @@ function parseHits(raw, type) {
|
|
|
345
345
|
});
|
|
346
346
|
}
|
|
347
347
|
const sessionArg = { session: z.string().describe("session handle from proc_open") };
|
|
348
|
-
const addrArg = z.union([z.string(), z.number()]).describe("address: \"0x
|
|
348
|
+
const addrArg = z.union([z.string(), z.number()]).describe("address: \"0x...\" hex, decimal, or \"module+rva\"");
|
|
349
349
|
const typeArg = z.string().describe("i8 u8 i16 u16 i32 u32 i64 u64 f32 f64 ptr str wstr bytes[:N]");
|
|
350
350
|
const toolDefs = {
|
|
351
351
|
proc_list: {
|
|
@@ -401,7 +401,7 @@ const toolDefs = {
|
|
|
401
401
|
}
|
|
402
402
|
},
|
|
403
403
|
mem_write: {
|
|
404
|
-
description: "Write a typed value (journaled
|
|
404
|
+
description: "Write a typed value (journaled -- reversible via mem_undo). dryRun:true returns {addr,current,would,bytes} WITHOUT writing.",
|
|
405
405
|
schema: {
|
|
406
406
|
...sessionArg,
|
|
407
407
|
addr: addrArg,
|
|
@@ -521,6 +521,62 @@ const toolDefs = {
|
|
|
521
521
|
};
|
|
522
522
|
}
|
|
523
523
|
},
|
|
524
|
+
mem_scan_pattern: {
|
|
525
|
+
description: "AOB signature scan: find all matches of a byte pattern like \"48 8B ?? 10\" (\"??\" wildcards). Returns {hits:[hexaddr],count,truncated}.",
|
|
526
|
+
schema: {
|
|
527
|
+
...sessionArg,
|
|
528
|
+
pattern: z.string().describe("byte pattern, hex bytes separated by spaces, \"??\" wildcards"),
|
|
529
|
+
module: z.string().optional().describe("restrict to a module"),
|
|
530
|
+
section: z.string().optional().describe("restrict to a PE section of `module` (e.g. \".text\")"),
|
|
531
|
+
start: addrArg.optional().describe("range start"),
|
|
532
|
+
end: addrArg.optional().describe("range end"),
|
|
533
|
+
onlyReadable: z.boolean().optional().describe("only scan readable regions (default true)"),
|
|
534
|
+
limit: z.number().int().positive().optional().describe("max hits returned (default 1000)")
|
|
535
|
+
},
|
|
536
|
+
handler: async (s, a) => {
|
|
537
|
+
const proc = s.get(a.session);
|
|
538
|
+
if (typeof a.pattern !== "string" || !a.pattern.trim()) throw fail("INVALID_ARGS", "mem_scan_pattern needs a pattern string");
|
|
539
|
+
const hits = await proc.scan(a.pattern, {
|
|
540
|
+
module: a.module,
|
|
541
|
+
section: a.section,
|
|
542
|
+
start: a.start !== void 0 ? parseAddress(proc, a.start, "start") : void 0,
|
|
543
|
+
end: a.end !== void 0 ? parseAddress(proc, a.end, "end") : void 0,
|
|
544
|
+
onlyReadable: a.onlyReadable
|
|
545
|
+
});
|
|
546
|
+
const limit = a.limit ?? 1e3;
|
|
547
|
+
return {
|
|
548
|
+
count: hits.length,
|
|
549
|
+
truncated: hits.length > limit,
|
|
550
|
+
hits: hits.slice(0, limit).map((h) => hex(h))
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
mem_regions: {
|
|
555
|
+
description: "VirtualQueryEx map of the target's memory regions: [{base,size,protect,state,type}] -- base/size are hex, protect/state/type are hex flag values. writableOnly/executableOnly filter on protection.",
|
|
556
|
+
schema: {
|
|
557
|
+
...sessionArg,
|
|
558
|
+
writableOnly: z.boolean().optional().describe("only regions whose protection allows writes"),
|
|
559
|
+
executableOnly: z.boolean().optional().describe("only regions whose protection allows execution")
|
|
560
|
+
},
|
|
561
|
+
handler: (s, a) => {
|
|
562
|
+
const proc = s.get(a.session);
|
|
563
|
+
const WRITABLE = 204;
|
|
564
|
+
const EXECUTABLE = 240;
|
|
565
|
+
let regions = proc.regions();
|
|
566
|
+
if (a.writableOnly === true) regions = regions.filter((r) => (r.protection & WRITABLE) !== 0);
|
|
567
|
+
if (a.executableOnly === true) regions = regions.filter((r) => (r.protection & EXECUTABLE) !== 0);
|
|
568
|
+
return {
|
|
569
|
+
count: regions.length,
|
|
570
|
+
regions: regions.map((r) => ({
|
|
571
|
+
base: hex(r.baseAddress),
|
|
572
|
+
size: hex(r.size),
|
|
573
|
+
protect: hex(r.protection),
|
|
574
|
+
state: hex(4096),
|
|
575
|
+
type: hex(r.type)
|
|
576
|
+
}))
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
},
|
|
524
580
|
mem_pointer_resolve: {
|
|
525
581
|
description: "Resolve module+rva plus a pointer chain: base = module base + rva, then dereference each offset (CE \"module.exe+RVA, offsets\" style).",
|
|
526
582
|
schema: {
|
|
@@ -609,8 +665,19 @@ const toolDefs = {
|
|
|
609
665
|
const addr = parseAddress(proc, a.addr);
|
|
610
666
|
const type = normType(a.type);
|
|
611
667
|
if (type === "str" || type === "wstr" || type === "bytes") throw fail("INVALID_ARGS", `freeze needs a numeric type, got "${a.type}"`);
|
|
612
|
-
const
|
|
613
|
-
|
|
668
|
+
const ms = a.ms ?? 50;
|
|
669
|
+
let stop;
|
|
670
|
+
if (type === "int8" || type === "uint8" || type === "int16" || type === "uint16") {
|
|
671
|
+
const v = Number(parseNumeric(a.value, false));
|
|
672
|
+
const write = type === "int8" || type === "uint8" ? () => proc.writeUInt8(addr, v & 255) : () => proc.writeUInt16(addr, v & 65535);
|
|
673
|
+
const timer = setInterval(() => {
|
|
674
|
+
try {
|
|
675
|
+
write();
|
|
676
|
+
} catch {}
|
|
677
|
+
}, ms);
|
|
678
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
679
|
+
stop = () => clearInterval(timer);
|
|
680
|
+
} else stop = proc.freeze(addr, type, parseNumeric(a.value, WIDE_TYPES.has(type)), ms);
|
|
614
681
|
return { freezeId: s.addFreeze(a.session, stop) };
|
|
615
682
|
}
|
|
616
683
|
},
|
|
@@ -623,7 +690,7 @@ const toolDefs = {
|
|
|
623
690
|
}
|
|
624
691
|
},
|
|
625
692
|
mem_undo: {
|
|
626
|
-
description: "Revert the last n journaled writes (default 1), most-recent first
|
|
693
|
+
description: "Revert the last n journaled writes (default 1), most-recent first -- the self-correction primitive.",
|
|
627
694
|
schema: {
|
|
628
695
|
...sessionArg,
|
|
629
696
|
n: z.number().int().positive().optional()
|
|
@@ -634,12 +701,12 @@ const toolDefs = {
|
|
|
634
701
|
}
|
|
635
702
|
},
|
|
636
703
|
mem_journal: {
|
|
637
|
-
description: "Snapshot of the write journal for this session: [{index,addr,bytes|null}]
|
|
704
|
+
description: "Snapshot of the write journal for this session: [{index,addr,bytes|null}] -- bytes is the pre-write hex that undo() restores.",
|
|
638
705
|
schema: sessionArg,
|
|
639
706
|
handler: (s, a) => s.get(a.session).writeLog
|
|
640
707
|
},
|
|
641
708
|
eval_js: {
|
|
642
|
-
description: "Escape hatch: run JS with the bound Process in scope as `proc`. Code is wrapped in an async body
|
|
709
|
+
description: "Escape hatch: run JS with the bound Process in scope as `proc`. Code is wrapped in an async body -- use `return` for a result, `await` is allowed.",
|
|
643
710
|
schema: {
|
|
644
711
|
...sessionArg,
|
|
645
712
|
code: z.string().describe("JS body; `proc` is the open Process")
|
|
@@ -683,7 +750,7 @@ async function callTool(sessions, name, args, opts = {}) {
|
|
|
683
750
|
}
|
|
684
751
|
// src/server.ts
|
|
685
752
|
/**
|
|
686
|
-
* @memoryprocess/mcp
|
|
753
|
+
* @memoryprocess/mcp -- MCP server exposing the memoryprocess toolkit to
|
|
687
754
|
* LLM agents over stdio (the primary agentic surface).
|
|
688
755
|
*
|
|
689
756
|
* Usage:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memoryprocess/mcp",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "MCP server exposing the memoryprocess toolkit to LLM agents
|
|
3
|
+
"version": "0.0.3-beta.2",
|
|
4
|
+
"description": "MCP server exposing the memoryprocess toolkit to LLM agents - the primary agentic surface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "bun@1.4.4",
|
|
7
7
|
"bin": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"agent"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@memoryprocess/core": "
|
|
48
|
+
"@memoryprocess/core": "workspace:*",
|
|
49
49
|
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
50
50
|
"zod": "^4.6.7"
|
|
51
51
|
},
|