A very simple/basic context layer system for LLM agents. Estratos is an MCP server that stores markdown memories in a filesystem-backed namespace tree and resolves context by walking ancestry from root to target, interleaving layer-wide and value-targeted crosscutting memories along the way.
Imagine you're managing a content marketing project (blog) in eg. Claude Cowork. As you iterate on content, you'll end up telling Claude about eg. your writing style — "conversational, second-person, short paragraphs, always open with a hook question" — and it'll be stored as a root memory that follows you across every project.
Inside your Cowork project called "Content", as you keep iterating on a blog post, you'll collect and save project-level context: warm and practical tone, target audience of early-stage founders, never mention competitors by name.
Then for each blog post you layer on blog-specific details: the angle, the target reader, relevant links/instructions. Three different layers of memory.
Two days later you start a fresh chat for a blog post and say "let's write a new blog post about hiring — observe the memories for root.content.blog" Claude calls Estratos, and the ancestry walk does the rest: your personal style, the brand voice, the audience personas, and the positioning rules, all load automatically without you repeating a word.
The following week, you start a help documentation project. You'll store the memories in root.content.helpdocs. When you start a new help document and load helpdocs, it'll also load your personal style, the brand voice, the audience personas, etc. alongisde the helpdocs memories.
Or if you're managing different brands, you could structure the memory layers as root.brand-a.content.blog and root.brand-b.content.blog.
And as these are text files, you can edit them any way you want (if you want to), or connect the MCP server to other agents so that memories are now shared.
The easiest path for Claude Desktop is the one-click extension bundle; every other client uses the npm package.
- Download the latest
estratos.mcpbfrom the releases page. - Double-click the file, or drag it onto a running Claude Desktop window.
- When prompted, pick a data directory (defaults to
~/.estratos/data) and leave "Multi-tenant mode" off unless you know you need it. - Claude Desktop restarts the extension automatically; the estratos tools appear in the tool picker.
No JSON editing, no manual npx wiring. Upgrades are a matter of dropping
in a newer .mcpb.
npm install -g estratosOr run on demand:
npx estratosEstratos speaks MCP over stdio, so any client that can launch a stdio MCP server can use it. The snippets below assume estratos is on your PATH (e.g. via npm install -g estratos); if not, substitute the absolute path or use npx -y estratos.
Edit your claude_desktop_config.json and add an entry under mcpServers. The file lives at:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
{
"mcpServers": {
"estratos": {
"command": "npx",
"args": ["-y", "estratos"],
"env": {
"ESTRATOS_DATA_DIR": "~/.estratos/data"
}
}
}
}Restart Claude Desktop. The estratos tools (store, retrieve, …) should appear in the tool picker.
To run estratos in multi-tenant mode, pass --multi-tenant in args and point ESTRATOS_DATA_DIR at a base directory:
{
"mcpServers": {
"estratos": {
"command": "npx",
"args": ["-y", "estratos", "--multi-tenant"],
"env": {
"ESTRATOS_DATA_DIR": "~/.estratos/tenants"
}
}
}
}Register the server with the claude mcp add command:
claude mcp add estratos -- npx -y estratosTo set environment variables or scope the server to your user config, use the flags:
claude mcp add estratos \
--scope user \
--env ESTRATOS_DATA_DIR=~/.estratos/data \
-- npx -y estratosYou can verify the server is registered with claude mcp list and inspect its tools from inside a Claude Code session.
Cursor reads MCP server definitions from ~/.cursor/mcp.json (global) or .cursor/mcp.json inside a project (per-project). Add:
{
"mcpServers": {
"estratos": {
"command": "npx",
"args": ["-y", "estratos"],
"env": {
"ESTRATOS_DATA_DIR": "~/.estratos/data"
}
}
}
}Open Settings → MCP in Cursor to confirm the server is connected and the tools are listed.
ChatGPT exposes MCP servers as custom connectors in Developer Mode (currently a beta feature available to ChatGPT Plus, Pro, and Business workspaces). One important constraint: ChatGPT only talks to MCP servers reachable over HTTP/SSE — it does not spawn local stdio processes — so estratos has to be fronted by an stdio→HTTP bridge such as supergateway or mcp-proxy.
-
Run estratos behind a stdio→HTTP bridge:
npx -y supergateway --stdio "npx -y estratos" --port 8765 -
If ChatGPT needs to reach the bridge from outside your machine, expose it over a public URL — for example with
ngrok http 8765. -
In ChatGPT, enable Developer Mode under Settings → Connectors → Advanced → Developer Mode. Workspace admins may need to allow it under Permissions & Roles → Connected Data → Developer mode / Create custom MCP connectors.
-
Open Settings → Connectors → Create, give the connector a name (e.g.
Estratos), point its URL at the bridge endpoint (e.g.https://<your-tunnel>.ngrok.app/sse), and save.
The estratos tools then appear in any ChatGPT conversation where the connector is enabled.
OpenClaw discovers MCP servers through its plugin system. Add an entry under mcpServers in ~/.openclaw/openclaw.json:
{
"mcpServers": {
"estratos": {
"command": "npx",
"args": ["-y", "estratos"],
"transport": "stdio",
"env": {
"ESTRATOS_DATA_DIR": "~/.estratos/data"
}
}
}
}Or register it through the CLI, which writes the same entry for you:
openclaw mcp add --transport stdio estratos -- npx -y estratosRestart your OpenClaw session; the estratos tools are picked up automatically. The exact location of the mcpServers block has moved across OpenClaw releases (some versions place it under plugins.mcp), so check openclaw mcp --help against the version you're running if the snippet above isn't picked up.
Most other MCP clients accept the same command / args / env shape used above. If your client expects a single command line, use npx -y estratos (or the absolute path to a globally installed estratos binary). The server has no required CLI arguments — defaults are taken from ESTRATOS_DATA_DIR and the optional config.json in that directory.
estratos [options]
-d, --data-dir <path> Path to the data root. Overrides ESTRATOS_DATA_DIR.
A leading '~' is expanded to the user's home dir.
In multi-tenant mode this is the BASE directory
that contains per-tenant subdirectories.
--multi-tenant Enable multi-tenant mode. See "Multi-tenancy" below.
-h, --help Show help and exit.
ESTRATOS_DATA_DIR— default data root. Defaults to~/.estratos/data. A leading~is expanded to the user's home directory. The--data-dirCLI flag takes precedence when both are set.ESTRATOS_MULTI_TENANT— set to1/true/yes/onto enable multi-tenant mode when--multi-tenantis not given. The CLI flag takes precedence when both are set.
Estratos uses a named hierarchy of layers to interpret namespace paths and to address crosscutting memories. Declare it in <data-dir>/config.json:
{
"hierarchy": ["root", "clients", "services", "jobs"]
}Layer names must be unique and may contain only letters, digits, -, and _. The array position is the layer depth: root is layer 1, clients is layer 2, and so on.
If config.json is absent, Estratos defaults to ["root"] (single layer) so you can start using it immediately.
A scoped memory lives at a single namespace node, e.g. root.acme-corp.audit.q3-review. Retrieving context for a path walks from the root down to that node, concatenating every scoped memory from general to specific.
A crosscutting memory is attached to a layer rather than a specific node. Any retrieval whose path reaches that layer includes the memory, regardless of which node value appears there. For hierarchy [root, clients, services, jobs], a memory stored at the services layer is applied to every request with at least three path segments — it sits between the scoped memories at depth 2 and depth 3.
You can also target a specific value at that layer. For the same hierarchy, a crosscutting memory stored at services with value = "content" applies only when the third segment is exactly content. That lets you say "save this for all content projects" while keeping paths like root.client-acme.content and root.client-cola.content.
Given a path with segments [s1, s2, …, sN] and hierarchy [h1, h2, …, hM], retrieval produces, in order:
scoped at s1
crosscutting at h1
crosscutting at h1=s1 (only if a value-targeted match exists)
scoped at s1.s2
crosscutting at h2
crosscutting at h2=s2 (only if a value-targeted match exists)
…
scoped at s1.s2…sN
crosscutting at hN (only if N ≤ M)
crosscutting at hN=sN (only if N ≤ M and a value-targeted match exists)
Each memory block is prefixed with a header identifying its source:
<!-- scoped: root.acme-corp | key: onboarding -->
<memory content here>
<!-- crosscutting: clients | key: contact-preferences -->
<memory content here>
<!-- crosscutting: services=content | key: writing-style -->
<memory content here>
| Tool | Parameters | Description |
|---|---|---|
store |
path, key, content |
Write a scoped memory. Overwrites if the key already exists at that path. |
store_crosscutting |
layer, key, content, value?, tenant? |
Write a crosscutting memory. Omit value to make it layer-wide; provide value to target one exact segment value at that layer. layer must exist in the configured hierarchy. tenant is required in multi-tenant mode, forbidden in single mode. |
retrieve |
path |
Return the concatenated context for path, ordered from general to specific. |
delete |
path, key |
Delete a scoped memory. |
delete_crosscutting |
layer, key, value?, tenant? |
Delete a crosscutting memory. Omit value to delete the layer-wide one; provide value to delete a targeted one. tenant is required in multi-tenant mode. |
list |
path |
List the keys stored at a specific scoped node (not ancestors or descendants). |
list_tree |
path? |
Return the namespace tree rooted at path (defaults to the first configured layer in single mode; path is required in multi-tenant mode). |
get_hierarchy |
tenant? |
Return the effective hierarchy as a JSON array. tenant is required in multi-tenant mode. |
set_hierarchy |
hierarchy, tenant? |
Set the hierarchy layers (e.g. ["root", "clients", "projects"]). Overwrites config.json and takes effect immediately. tenant is required in multi-tenant mode. |
In multi-tenant mode, tools that take path interpret the first segment as the tenant identifier (e.g. alice.root.acme.audit). Tools that take layer instead accept an explicit tenant field.
<data-dir>/
config.json
scoped/
root/
memory-a.md
acme-corp/
memory-b.md
audit/
memory-c.md
crosscutting/
root/
__all__/
memory-e.md
clients/
__all__/
memory-f.md
services/
__all__/
memory-g.md
__match__/
content/
memory-h.md
<base-dir>/
config.json (optional: default hierarchy for tenants without their own)
alice/
config.json (optional: alice's own hierarchy, overrides the base default)
scoped/
root/
memory-a.md
crosscutting/
services/
__all__/
memory-g.md
__match__/
content/
memory-h.md
bob/
scoped/
crosscutting/
Each tenant subdirectory is a self-contained Estratos data root: its own optional config.json, its own scoped/ tree, its own crosscutting/ tree. There is zero data sharing across tenants.
Estratos can serve many logical tenants from a single process, the way a Postgres server hosts many schemas. Enable it with --multi-tenant (or ESTRATOS_MULTI_TENANT=1) and point --data-dir at the base directory. Each request then identifies its tenant:
- Path-based tools (
store,retrieve,delete,list,list_tree): the first segment ofpathis the tenant.alice.root.acme.auditmeans "tenantalice, scoped pathroot.acme.audit". - Crosscutting tools (
store_crosscutting,delete_crosscutting): take an explicittenantparameter alongsidelayer, plus an optionalvaluewhen you want to target one exact segment value at that layer.
Each tenant can supply its own <base-dir>/<tenant>/config.json with its own hierarchy. When a tenant has none, Estratos falls back to the hierarchy from <base-dir>/config.json, and ultimately to ["root"]. Tenant hierarchies are loaded on first access and cached for the lifetime of the process — editing a tenant's config file requires a server restart.
Estratos has no authentication or authorization. The embedding application is trusted to pass the correct tenant on every call — the same way an application is trusted to pick the correct database or schema. Multi-tenant mode defends against accidental cross-pollution (filesystem bugs, path traversal, logic errors); it is not a defense against a malicious MCP client that lies about its identity. If your deployment requires that, you need a different product: bearer tokens, HTTP transport, and a real auth layer.
- Scoped ancestry walks stay within a tenant. Retrieving
alice.root.acme.service-aonly reads files under<base-dir>/alice/scoped/root/acme/service-a/and its ancestors up to<base-dir>/alice/scoped/root/. It cannot surface data frombob.*or from sibling paths likealice.root.example.*. - Crosscutting memories are tenant-local. They live inside each tenant's
crosscutting/directory, so alice's crosscutting at the services layer never appears in bob's retrieves. - Per-tenant hierarchies are independent. A layer name valid for alice but absent from bob's hierarchy is rejected when bob tries to use it — each tenant's
store_crosscuttingvalidates against its own hierarchy. - Path-traversal hardening. Tenant identifiers pass the same character-class regex as every other segment (
^[a-zA-Z0-9_-]+$), so..,/, and friends are rejected before any disk I/O.
Estratos does not mix modes against the same directory. If you enable multi-tenant mode, point it at a directory structured for multi-tenant use, not one that already contains single-tenant scoped//crosscutting/ data. Automatic migration between modes is not supported.
- Path segments and memory keys must match
^[a-zA-Z0-9_-]+$. - Paths are dot-separated, may not start or end with a dot, and may not contain empty segments.
layerarguments to crosscutting tools must appear in the configured hierarchy.
Violations produce clear MCP error responses (isError: true) with actionable messages.
npm install
npm run build
npm testAll tests run against real tmp directories (no mocked filesystem) via vitest.
npm run validate:mcpb # sanity-check manifest.json
npm run pack:mcpb # builds dist/ and writes estratos.mcpbReleases are managed by release-please. There is no manual version bumping, tagging, or asset uploading.
- Write commits in Conventional Commits
format.
feat:→ minor bump,fix:→ patch bump,feat!:orBREAKING CHANGE:→ minor bump (while pre-1.0) / major bump (post-1.0).docs:,chore:,refactor:,test:don't move the version on their own but do appear in the changelog. - Merge PRs to
mainnormally. On every push tomain, theReleaseGitHub Actions workflow runs release-please, which opens (or updates) a single "Release v0.X.Y" PR summarising every Conventional Commit since the last tag. The Release PR contains thepackage.json+manifest.jsonversion bump and a regeneratedCHANGELOG.md— review it like any other PR. - Merge the Release PR when you're ready to cut a release. Merging it
makes release-please create the git tag
vX.Y.Zand a GitHub Release with auto-generated notes. In the same workflow run, thebuild-mcpbjob then buildsdist/, prunes dev dependencies, packsestratos.mcpbvia@anthropic-ai/mcpb, and attaches it as an asset on the freshly created release.
Config lives in release-please-config.json (bump-minor-pre-major: true
keeps us in 0.x until we explicitly cut a 1.0.0) and
.release-please-manifest.json (tracks the last released version). The
workflow lives at .github/workflows/release.yml.
MIT © Celso Pinto