@memoryprocess/mcp 0.0.3-beta.9 → 0.1.0-beta.0
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/LICENSE +21 -0
- package/README.md +35 -21
- package/dist/server.mjs +35 -11
- package/package.json +5 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 JoShMiQueL
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -25,6 +25,11 @@ memoryprocess-mcp [--no-eval] [--help] [--version]
|
|
|
25
25
|
MEMP_NO_EVAL=1 # same as --no-eval
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
`--no-eval` runs **restricted mode**: `eval_js` AND the write tools
|
|
29
|
+
(`mem_write`, `mem_freeze`, `mem_undo`) are hidden from `tools/list` and
|
|
30
|
+
rejected by name -- the model gets a read/describe/scan/pointer/watch
|
|
31
|
+
surface only, nothing that mutates the target.
|
|
32
|
+
|
|
28
33
|
## Session model
|
|
29
34
|
|
|
30
35
|
There is no implicit "current process". `proc_open` returns a session handle
|
|
@@ -34,25 +39,27 @@ watches/freezes bound to it.
|
|
|
34
39
|
|
|
35
40
|
## Tools
|
|
36
41
|
|
|
37
|
-
| Tool | Description
|
|
38
|
-
| --------------------- |
|
|
39
|
-
| `proc_list` | List running processes `{pid,name}[]`
|
|
40
|
-
| `proc_open` | Open by `{pid}` or `{name}` -> `{session, describe}`. `access:"ro"` = read-only
|
|
41
|
-
| `proc_close` | Close a session
|
|
42
|
-
| `mem_describe` | `Process.describe()`: arch, modules, writable/exec regions, threads
|
|
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
|
-
| `mem_scan_next` | Refine hits: `exact`/`changed`/`unchanged`/`increased`/`decreased`/`increasedBy`/`decreasedBy`/`between`
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
42
|
+
| Tool | Description |
|
|
43
|
+
| --------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
44
|
+
| `proc_list` | List running processes `{pid,name}[]` |
|
|
45
|
+
| `proc_open` | Open by `{pid}` or `{name}` -> `{session, describe}`. `access:"ro"` = read-only |
|
|
46
|
+
| `proc_close` | Close a session |
|
|
47
|
+
| `mem_describe` | `Process.describe()`: arch, modules, writable/exec regions, threads |
|
|
48
|
+
| `mem_read` | Typed read: `i8...i64/u8...u64`, `f32/f64`, `ptr`, `str`, `wstr`, `bytes[:N]` (+`len`) |
|
|
49
|
+
| `mem_write` | Typed write. `dryRun:true` -> `{addr,current,would,bytes}` preview. Journaled |
|
|
50
|
+
| `mem_scan` | CE value scan -> `{hits:[{addr,value}],count,truncated}` (`module`/`start`/`end`/`limit`) |
|
|
51
|
+
| `mem_scan_next` | Refine hits: `exact`/`changed`/`unchanged`/`increased`/`decreased`/`increasedBy`/`decreasedBy`/`between` |
|
|
52
|
+
| `mem_scan_pattern` | AOB signature scan (`"48 8B ?? 10"`) -> `{hits,count,truncated}` (`module`/`section`/`start`/`end`/`limit`) |
|
|
53
|
+
| `mem_regions` | VirtualQueryEx region map `{base,size,protect,state,type}` (`writableOnly`/`executableOnly` filters) |
|
|
54
|
+
| `mem_pointer_resolve` | `module`+`rva`+optional `offsets[]` -> resolved address (CE pointer syntax; no offsets = module base+rva) |
|
|
55
|
+
| `mem_watch` | Poll an i32 every `ms` -> `{watchId}`; changes accumulate |
|
|
56
|
+
| `mem_watch_poll` | Drain recorded changes `[{value,ts}]` for a `watchId` |
|
|
57
|
+
| `mem_unwatch` | Stop a watch |
|
|
58
|
+
| `mem_freeze` | Re-write a value every `ms` -> `{freezeId}` |
|
|
59
|
+
| `mem_unfreeze` | Stop a freeze |
|
|
60
|
+
| `mem_undo` | Revert last `n` journaled writes (self-correction primitive) |
|
|
61
|
+
| `mem_journal` | Write-log snapshot `[{index,addr,bytes}]` |
|
|
62
|
+
| `eval_js` | Run JS with `proc` (`Process`) in scope - the escape hatch |
|
|
56
63
|
|
|
57
64
|
## Wire format (docs/json-conventions.md)
|
|
58
65
|
|
|
@@ -70,5 +77,12 @@ watches/freezes bound to it.
|
|
|
70
77
|
All writes are journaled -> `mem_journal` + `mem_undo` let an agent preview
|
|
71
78
|
(`dryRun`), commit, inspect, and revert. `eval_js` runs arbitrary JS on the
|
|
72
79
|
host - it's on by default (agents already have code execution) and can be
|
|
73
|
-
disabled with `--no-eval` / `MEMP_NO_EVAL=1`, which
|
|
74
|
-
`tools
|
|
80
|
+
disabled with `--no-eval` / `MEMP_NO_EVAL=1`, which is the **restricted
|
|
81
|
+
mode**: `eval_js` and the write tools (`mem_write`, `mem_freeze`,
|
|
82
|
+
`mem_undo`) are omitted from `tools/list` entirely, leaving a
|
|
83
|
+
read/describe/scan/pointer/watch surface that cannot mutate the target.
|
|
84
|
+
|
|
85
|
+
The coarse tool surface is intentionally narrow: `alloc`, `protect`,
|
|
86
|
+
`injectDll`, thread control, `hook`, `callRemote`, `readMany`/`entity` live
|
|
87
|
+
in `eval_js` by design (escape hatch), and are therefore unreachable under
|
|
88
|
+
`--no-eval` -- that is the point of restricted mode.
|
package/dist/server.mjs
CHANGED
|
@@ -414,6 +414,7 @@ const toolDefs = {
|
|
|
414
414
|
dryRun: z.boolean().optional().describe("preview only, do not write")
|
|
415
415
|
},
|
|
416
416
|
destructive: true,
|
|
417
|
+
writeOnly: true,
|
|
417
418
|
handler: (s, a) => {
|
|
418
419
|
const proc = s.get(a.session);
|
|
419
420
|
const r = writeTyped(proc, parseAddress(proc, a.addr), a);
|
|
@@ -583,7 +584,7 @@ const toolDefs = {
|
|
|
583
584
|
...sessionArg,
|
|
584
585
|
module: z.string().describe("module basename e.g. \"victim.exe\""),
|
|
585
586
|
rva: z.union([z.string(), z.number()]).describe("offset from module base (hex ok)"),
|
|
586
|
-
offsets: z.array(z.union([z.string(), z.number()])).describe("pointer-chain offsets;
|
|
587
|
+
offsets: z.array(z.union([z.string(), z.number()])).optional().describe("pointer-chain offsets; omit (or []) for plain module+rva -- no dereference")
|
|
587
588
|
},
|
|
588
589
|
handler: (s, a) => {
|
|
589
590
|
const proc = s.get(a.session);
|
|
@@ -595,7 +596,7 @@ const toolDefs = {
|
|
|
595
596
|
} catch {
|
|
596
597
|
throw fail("INVALID_ARGS", `bad rva "${a.rva}"`);
|
|
597
598
|
}
|
|
598
|
-
const offsets = a.offsets.map((o, i) => {
|
|
599
|
+
const offsets = (a.offsets ?? []).map((o, i) => {
|
|
599
600
|
const n = typeof o === "string" ? Number(BigInt(o)) : Number(o);
|
|
600
601
|
if (!Number.isFinite(n)) throw fail("INVALID_ARGS", `offsets[${i}] invalid`);
|
|
601
602
|
return n;
|
|
@@ -660,6 +661,7 @@ const toolDefs = {
|
|
|
660
661
|
ms: z.number().int().positive().optional()
|
|
661
662
|
},
|
|
662
663
|
destructive: true,
|
|
664
|
+
writeOnly: true,
|
|
663
665
|
handler: (s, a) => {
|
|
664
666
|
const proc = s.get(a.session);
|
|
665
667
|
const addr = parseAddress(proc, a.addr);
|
|
@@ -696,6 +698,7 @@ const toolDefs = {
|
|
|
696
698
|
n: z.number().int().positive().optional()
|
|
697
699
|
},
|
|
698
700
|
destructive: true,
|
|
701
|
+
writeOnly: true,
|
|
699
702
|
handler: (s, a) => {
|
|
700
703
|
return { reverted: s.get(a.session).undo(a.n ?? 1) };
|
|
701
704
|
}
|
|
@@ -725,16 +728,19 @@ const toolDefs = {
|
|
|
725
728
|
}
|
|
726
729
|
};
|
|
727
730
|
const TOOL_DEFS = toolDefs;
|
|
731
|
+
/** Tools visible under the given mode. restricted = --no-eval/MEMP_NO_EVAL. */
|
|
728
732
|
function toolNames(opts = {}) {
|
|
729
733
|
const evalEnabled = opts.evalEnabled ?? true;
|
|
730
|
-
|
|
734
|
+
const restricted = opts.restricted ?? false;
|
|
735
|
+
return Object.keys(toolDefs).filter((n) => (evalEnabled || n !== "eval_js") && (!restricted || !toolDefs[n].writeOnly));
|
|
731
736
|
}
|
|
732
737
|
/** Direct dispatcher used by both the stdio server and the tests. */
|
|
733
738
|
async function callTool(sessions, name, args, opts = {}) {
|
|
734
739
|
try {
|
|
735
740
|
const evalEnabled = opts.evalEnabled ?? true;
|
|
741
|
+
const restricted = opts.restricted ?? false;
|
|
736
742
|
const def = toolDefs[name];
|
|
737
|
-
if (!def || name === "eval_js" && !evalEnabled) throw fail("UNKNOWN_TOOL", `unknown tool "${name}"`);
|
|
743
|
+
if (!def || name === "eval_js" && !evalEnabled || restricted && def.writeOnly) throw fail("UNKNOWN_TOOL", `unknown tool "${name}"`);
|
|
738
744
|
const parsed = z.object(def.schema).safeParse(args ?? {});
|
|
739
745
|
if (!parsed.success) {
|
|
740
746
|
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
@@ -755,7 +761,8 @@ async function callTool(sessions, name, args, opts = {}) {
|
|
|
755
761
|
*
|
|
756
762
|
* Usage:
|
|
757
763
|
* memoryprocess-mcp # serve MCP over stdio
|
|
758
|
-
* memoryprocess-mcp --no-eval #
|
|
764
|
+
* memoryprocess-mcp --no-eval # restricted mode: no eval_js and no write
|
|
765
|
+
* # tools (read/describe/scan/watch/undo only)
|
|
759
766
|
* memoryprocess-mcp --help # list tools + exit
|
|
760
767
|
* memoryprocess-mcp --version
|
|
761
768
|
*
|
|
@@ -765,6 +772,7 @@ const dir = dirname(fileURLToPath(import.meta.url));
|
|
|
765
772
|
const pkg = JSON.parse(readFileSync(join(dir, "..", "package.json"), "utf8"));
|
|
766
773
|
const argv = process.argv.slice(2);
|
|
767
774
|
const evalEnabled = !argv.includes("--no-eval") && process.env.MEMP_NO_EVAL !== "1";
|
|
775
|
+
const restricted = !evalEnabled;
|
|
768
776
|
if (argv.includes("--version") || argv.includes("-v")) {
|
|
769
777
|
console.log(`${pkg.name} ${pkg.version}`);
|
|
770
778
|
process.exit(0);
|
|
@@ -775,27 +783,37 @@ if (argv.includes("--help") || argv.includes("-h")) {
|
|
|
775
783
|
console.log("");
|
|
776
784
|
console.log("Usage: memoryprocess-mcp [--no-eval] [--help] [--version]");
|
|
777
785
|
console.log(" MEMP_NO_EVAL=1 is equivalent to --no-eval");
|
|
786
|
+
console.log(" --no-eval = restricted mode: eval_js AND the write tools");
|
|
787
|
+
console.log(" (mem_write, mem_freeze, mem_undo) are hidden. Read,");
|
|
788
|
+
console.log(" describe, regions, scans, pointers and watches remain.");
|
|
778
789
|
console.log("");
|
|
779
790
|
console.log("Session model: proc_open returns a handle; every mem_* tool");
|
|
780
791
|
console.log("takes it as `session`. Writes are journaled (mem_journal) and");
|
|
781
792
|
console.log("reversible (mem_undo); mem_write supports dryRun previews.");
|
|
782
793
|
console.log("");
|
|
783
794
|
console.log("Tools:");
|
|
784
|
-
const names = toolNames({
|
|
795
|
+
const names = toolNames({
|
|
796
|
+
evalEnabled,
|
|
797
|
+
restricted
|
|
798
|
+
});
|
|
785
799
|
const width = Math.max(...names.map((n) => n.length));
|
|
786
800
|
for (const n of names) {
|
|
787
801
|
const def = TOOL_DEFS[n];
|
|
788
802
|
console.log(` ${n.padEnd(width)} ${def.description}`);
|
|
789
803
|
}
|
|
790
|
-
if (
|
|
804
|
+
if (restricted) console.log("\n(restricted mode: eval_js and write tools hidden -- read/describe/scan only)");
|
|
805
|
+
else if (!evalEnabled) console.log("\n(eval_js is disabled by --no-eval / MEMP_NO_EVAL=1)");
|
|
791
806
|
process.exit(0);
|
|
792
807
|
}
|
|
793
808
|
const sessions = createSessions();
|
|
794
809
|
const server = new McpServer({
|
|
795
810
|
name: pkg.name,
|
|
796
811
|
version: pkg.version
|
|
797
|
-
}, { instructions: "Process-memory toolkit. proc_open a target to get a session handle; all mem_* tools take it. Writes are journaled: mem_write with dryRun:true previews, mem_undo reverts. Prefer the coarse tools; eval_js is the escape hatch for anything else." });
|
|
798
|
-
for (const name of toolNames({
|
|
812
|
+
}, { instructions: "Process-memory toolkit. proc_open a target to get a session handle; all mem_* tools take it. Writes are journaled: mem_write with dryRun:true previews, mem_undo reverts. Prefer the coarse tools; eval_js is the escape hatch for anything else (alloc/inject/threads/hooks/readMany live there by design -- the coarse tool surface stays narrow on purpose). --no-eval runs restricted: read/describe/scan/watch only, no write tools." });
|
|
813
|
+
for (const name of toolNames({
|
|
814
|
+
evalEnabled,
|
|
815
|
+
restricted
|
|
816
|
+
})) {
|
|
799
817
|
const def = TOOL_DEFS[name];
|
|
800
818
|
server.registerTool(name, {
|
|
801
819
|
description: def.description,
|
|
@@ -805,7 +823,10 @@ for (const name of toolNames({ evalEnabled })) {
|
|
|
805
823
|
destructiveHint: def.destructive === true
|
|
806
824
|
}
|
|
807
825
|
}, async (args) => {
|
|
808
|
-
const result = await callTool(sessions, name, args, {
|
|
826
|
+
const result = await callTool(sessions, name, args, {
|
|
827
|
+
evalEnabled,
|
|
828
|
+
restricted
|
|
829
|
+
});
|
|
809
830
|
const data = result.ok === true ? result.data !== null && typeof result.data === "object" && !Array.isArray(result.data) ? result.data : { result: result.data } : void 0;
|
|
810
831
|
return {
|
|
811
832
|
content: [{
|
|
@@ -828,5 +849,8 @@ process.on("SIGINT", shutdown);
|
|
|
828
849
|
process.on("SIGTERM", shutdown);
|
|
829
850
|
const transport = new StdioServerTransport();
|
|
830
851
|
await server.connect(transport);
|
|
831
|
-
process.stderr.write(`${pkg.name} ${pkg.version} ready on stdio (${toolNames({
|
|
852
|
+
process.stderr.write(`${pkg.name} ${pkg.version} ready on stdio (${toolNames({
|
|
853
|
+
evalEnabled,
|
|
854
|
+
restricted
|
|
855
|
+
}).length} tools${restricted ? ", restricted: no eval/write" : ""})\n`);
|
|
832
856
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memoryprocess/mcp",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
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",
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
".": "./dist/server.mjs"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
|
-
"dist"
|
|
14
|
+
"dist",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"README.md"
|
|
15
17
|
],
|
|
16
18
|
"os": [
|
|
17
19
|
"win32"
|
|
@@ -45,7 +47,7 @@
|
|
|
45
47
|
"agent"
|
|
46
48
|
],
|
|
47
49
|
"dependencies": {
|
|
48
|
-
"@memoryprocess/core":
|
|
50
|
+
"@memoryprocess/core":"^0.1.0-beta.0",
|
|
49
51
|
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
50
52
|
"zod": "^4.6.7"
|
|
51
53
|
},
|