Mnemonica Strategy
@mnemonica/strategy
MCP (Model Context Protocol) server that lets an AI agent work on a running Mnemonica runtime — inspect its type tree, define new types, construct instances, and swap constructor handlers in flight — without stopping the server you are developing.
Overview
Strategy is the live bridge between a running Mnemonica runtime and the
tools around it. It attaches to a target Node.js process via the Chrome
Debug Protocol (CDP) — zero instrumentation of the target — and then moves
the real work onto a fast WebSocket construction channel injected into the
process. It is the central point of the topology: agents drive it over MCP;
Mnemographica connects as
the monitoring client — through Strategy's own trace channel, or DIRECTLY to
an app's self-hosted channel (startStrategyClient + traceSubscribe, the
App Channel tab — no CDP in between).
The development loop this enables — the main server never stops:
Your app runs as usual (a debug-enabled child copy via infer-debug works too).
Strategy attaches over CDP — once. CDP is the delivery truck, not the road.
A dependency-free WebSocket server is injected into the runtime; all construction traffic (
define/instantiate/swap) moves there.New types are born shimmed: their constructor is a stable shell whose handler lives in the session's closure, so
ws_swapcan replace the implementation in flight — existing constructors and instances are untouched.When a shape is proven, Tactica crystallizes it into
.tacticatype definitions.
Strategy can also compare the runtime type tree against Tactica-generated types to validate static analysis — its original purpose, still available.
Installation
npm install @mnemonica/strategyFrom source instead:
git clone https://github.com/mythographica/strategy.git
cd strategy
npm install
npm run buildUsage
Prerequisites
Your target application must be running with the debug flag:
# For NestJS
nest start --debug --watch
# For regular Node.js
node --inspect=9229 your-app.jsDon't want --inspect on the main process? infer-debug
can spawn a debug-enabled child copy of the app on demand and tunnel CDP
through the app's own HTTP port — strategy attaches to that child the same
way.
As MCP Server
# installed from npm
npx @mnemonica/strategy
# from a source checkout
node /path/to/strategy/lib/cli.jsMCP Configuration
Add to your agent framework's MCP config:
{
"mcpServers": {
"mnemonica-strategy": {
"command": "npx",
"args": ["-y", "@mnemonica/strategy"]
}
}
}or, from a source checkout:
{
"mcpServers": {
"mnemonica-strategy": {
"command": "node",
"args": ["/path/to/strategy/lib/cli.js"]
}
}
}MCP Tools Provided
The Strategy MCP server exposes 3 bundled tools:
1. execute
Execute any command from the 3 context folders (MCP, RPC, RUN).
Input:
context(string, required): Execution context - "MCP", "RPC", or "RUN"command(string, required): Command name to executemessage(string, optional): JSON string containing command arguments
Example:
// Connect to Node.js debugger
execute {
context: "RPC",
command: "rpc_connection",
message: "{ \"action\": \"connect\", \"host\": \"localhost\", \"port\": 9229 }"
}
// Check connection status
execute {
context: "RPC",
command: "rpc_connection",
message: "{ \"action\": \"status\" }"
}
// Analyze the runtime type hierarchy
execute {
context: "RPC",
command: "rpc_analyze_type_hierarchy",
message: "{}"
}2. list
List available commands by context.
Input:
context(string, required): "MCP", "RPC", "RUN", or "ALL"
Example:
list { context: "ALL" }3. help
Get detailed help for any command.
Input:
context(string, required): Command contextcommand(string, required): Command name
Example:
help { context: "RPC", command: "rpc_connection" }Args Passing Mechanism (IMPORTANT)
Due to MCP protocol limitations, command arguments must be passed as a JSON string in the message field, not as direct object properties.
Correct format:
execute {
context: "RPC",
command: "rpc_connection",
message: "{ \"action\": \"connect\", \"host\": \"localhost\", \"port\": 9229 }"
}Incorrect format (will not work):
// DON'T DO THIS
execute {
context: "RPC",
command: "rpc_connection",
args: { action: "connect" } // This won't work!
}Common Commands
Connection Management
// Connect to Node.js debugger
execute {
context: "RPC",
command: "rpc_connection",
message: "{ \"action\": \"connect\", \"host\": \"localhost\", \"port\": 9229 }"
}
// Check connection status
execute {
context: "RPC",
command: "rpc_connection",
message: "{ \"action\": \"status\" }"
}
// Disconnect from runtime
execute {
context: "RPC",
command: "rpc_connection",
message: "{ \"action\": \"disconnect\" }"
}Type Analysis
// Analyze the complete type hierarchy (recursive subtype tree from the target)
execute {
context: "RPC",
command: "rpc_analyze_type_hierarchy",
message: "{}"
}
// Create type in the target runtime via CDP
execute {
context: "RPC",
command: "rpc_create_type",
message: "{ \"typeName\": \"MyType\" }"
}
// Load Tactica-generated types
execute {
context: "MCP",
command: "mcp_load_remote_tactica_types",
message: "{ \"projectPath\": \"/path/to/project\" }"
}
// Compare runtime vs Tactica types
execute {
context: "MCP",
command: "mcp_compare_with_tactica",
message: "{ \"projectPath\": \"/path/to/project\" }"
}Example Workflow
Start your Mnemonica application with debug mode:
# any Mnemonica app, e.g. a NestJS service nest start --debug --watch # or plain Node.js node --inspect=9229 your-app.jsConnect to the debugger:
execute { context: "RPC", command: "rpc_connection", message: "{ \"action\": \"connect\" }" }Analyze runtime types:
execute { context: "RPC", command: "rpc_analyze_type_hierarchy", message: "{}" }Compare with Tactica-generated types:
execute { context: "MCP", command: "mcp_compare_with_tactica", message: "{ \"projectPath\": \"/path/to/project\" }" }
The construction channel (ws_ commands)
After rpc_connection is up, one call injects the WS channel into the
target; everything after that is fast WS traffic, not CDP:
// 1. Bootstrap: inject the WS server into the target (one CDP evaluate)
execute { context: "RPC", command: "ws_bootstrap", message: "{}" }
// 2. Define a type — born shimmed (swappable later)
execute {
context: "MCP",
command: "ws_define",
message: "{ \"name\": \"TempProbe\", \"body\": \"function (data) { this.value = data.value; }\" }"
}
// 3. Construct an instance of it, right now, in the running process
execute {
context: "MCP",
command: "ws_instantiate",
message: "{ \"path\": \"TempProbe\", \"args\": [{ \"value\": 42 }] }"
}
// → { chain: ["Mnemonica", "TempProbe"], props: { value: 42 } }
// 4. Swap the handler in flight — the constructor identity never changes
execute {
context: "MCP",
command: "ws_swap",
message: "{ \"path\": \"TempProbe\", \"body\": \"function (data) { this.value = data.value * 2; }\" }"
}
// 5. Next instance uses the NEW implementation
execute {
context: "MCP",
command: "ws_instantiate",
message: "{ \"path\": \"TempProbe\", \"args\": [{ \"value\": 42 }] }"
}
// → { props: { value: 84 } }
// Session state: which types are shimmed/swappable
execute { context: "MCP", command: "ws_session", message: "{ \"action\": \"list\" }" }Rules the channel enforces (they are mnemonica semantics, not policy):
ws_swaprefuses any type not born viaws_definein this session — pre-existing types are never re-defined.Subtypes construct from parent instances: for nested paths,
ws_instantiatewalks the chain, taking intermediate constructor args fromchainArgs(e.g.{ "TempProbe": [{ "value": 7 }] }).Async constructor handlers must
return this(mnemonica enforces this).
The in-target server is development-only instrumentation: it binds
127.0.0.1, requires a per-session token at the WebSocket handshake, and
disappears with the process. Do not expose it on production runtimes.
Command Contexts
Context | Folder | Execution Environment |
MCP |
| Local MCP server process |
RPC |
| Local orchestration; effects in the target via CDP |
RUN |
| Local side effects (files, utilities) |
Command names carry their site as a prefix (mcp_, rpc_, run_), so
the place of execution is visible in the name itself. The ws_ prefix
marks the construction channel: ws_bootstrap lives in commands-rpc/
(it needs CDP to get in), every other ws_* command lives in
commands-mcp/ and talks to the stored WS session.
Development
# Install dependencies
npm install
# Build
npm run build
# Watch mode
npm run watch
# Test
npm run testCDP Scripts Architecture
The cdp-scripts/ folder contains scripts that execute inside the target Node.js runtime via Chrome Debug Protocol:
cdp-scripts/
├── create-type.js # Creates mnemonica types in the target
├── analyze-hierarchy.js # Retrieves complete type hierarchy
└── ws-server.js # Phase 3: the injected WS construction serverHow it works:
MCP command reads the script file
(create-type only) injects
var args = {...}at the top with command argumentsSends it to the target via
client.Runtime.evaluate({ expression: script, awaitPromise: true })Script executes inside the target process
Return value is sent back to the MCP process
Key pattern — the canonical prelude. Scripts must never use a bare
require (there is none in evaluated code) and never rely on
process.mainModule.require alone (it is undefined in ESM-entry
processes, and import() in evaluated code throws
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING). Every cdp-script loads the
target's own mnemonica through this exact three-tier prelude:
var mnemonica;
if (process.mainModule && process.mainModule.require) {
mnemonica = process.mainModule.require('mnemonica');
} else if (typeof process.getBuiltinModule === 'function') {
var nodeModule = process.getBuiltinModule('node:module');
var cwdRequire = nodeModule.createRequire(process.cwd() + '/__strategy_cwd__.js');
mnemonica = cwdRequire('mnemonica');
} else {
var mnemonicaNs = await import('mnemonica');
mnemonica = mnemonicaNs.default || mnemonicaNs;
}Scripts using it must be async IIFEs. ws-server.js generalizes the same
three tiers into a targetRequire factory because it also needs
node:http/node:crypto — see AGENTS.md for the variant rule.
// Access types via the defaultCollection Map (avoids proxy enumeration issues)
mnemonica.defaultCollection.forEach(function (Type, name) {
// Process each type
});
// Recursive traversal for subtype hierarchy
function getSubtypes (Type) {
var subtypes = [];
Type.subtypes.forEach(function (SubType, name) {
subtypes.push({
name: name,
subtypes: getSubtypes(SubType) // Recursive
});
});
return subtypes;
}Creating Commands
Commands are JavaScript files in the commands-*/ folders with MCP Tool Metadata.
Two shapes are supported; module.exports.run is the pattern current
commands use:
/**
* MCP Tool Metadata:
* {
* "name": "mcp_my_command",
* "description": "What this command does",
* "inputSchema": {
* "type": "object",
* "properties": {
* "argName": { "type": "string" }
* }
* }
* }
*/
async function run (ctx) {
const { require, args, store } = ctx;
// Parse message if present (args arrive as a JSON string in `message`)
let commandArgs = args;
if (args.message && typeof args.message === 'string') {
try {
commandArgs = JSON.parse(args.message);
} catch (e) {
return { success: false, error: 'Invalid JSON: ' + e.message };
}
}
return { success: true, data: { got: commandArgs.argName } };
}
module.exports = { run };Files without a run export are instead wrapped in an async IIFE with
ctx in scope. Two gotchas, both learned the hard way:
Never
ctx.require('mnemonica')— that resolves in the MCP process, not the target. Target-side mnemonica work belongs incdp-scripts/with the canonical prelude.ctx.requireresolves relative paths fromlib/server.js, not from your command file — require lib modules by absolute path (path.join(__dirname, '../../lib/...')), the same idiom used forcdp-scripts/.
License
MIT