AICortex
Exposes AICortex tools through a native REST API and OpenAPI spec so LangChain agents can call them.
Connects to MQTT devices/services, allowing AI tools to interact with MQTT-based devices and integrations.
Exposes AICortex tools through a native REST API and OpenAPI spec so n8n workflows can call them.
Uses a local Ollama model as a runner for scheduled jobs, enabling local-only execution of due tasks.
Uses a ChatGPT plan or API key as a runner for scheduled jobs.
Sends scanned documents from eSCL LAN scanners directly into Paperless-ngx.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@AICortexremember that I prefer concise replies and use it in future chats"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
In 30 seconds: AICortex is one container on your own hardware that every AI you use connects to.
It remembers who you are, how you work and what you're building — and hands that to Claude, ChatGPT or a local model at the start of every chat, on desktop and phone.
It reaches your stuff — services, devices, mail, calendars, files — with the secrets in an encrypted vault the model never sees.
It keeps your schedule: tell your AI "tomorrow at 8: summarise yesterday's notes". AICortex stores the job and at 8 wakes your Claude (or asks ChatGPT, an API key or a local Ollama) to do it.
AICortex is a self-hosted MCP server that turns your NAS into a personal LLM connector: a persistent "brain" your assistant loads at the start of every session. Any MCP-capable LLM — Claude, ChatGPT, or a fully local model — gains a durable identity and real reach into your own services and devices, while running inside your network. Non-MCP tools (n8n, LangChain, your own scripts) reach the same brain through a native REST API with scoped per-user keys — and humans get a built-in admin WebUI at /ui for the vault, skills, users and audit log.
The model stays in its provider's cloud (or runs locally). Your memory, skills and secrets stay on your NAS. The assistant talks to AICortex over an HTTPS connector; the server uses your local credentials internally and never hands them to the model. New capabilities are added as data — a skill, a service config, a secret — with no redeploy.
Highlights
🧠 A brain that travels with you
One short
cortex_startcall — who you are, how you work, and the current project's notes, open tasks and last session (a few hundred characters;cortex_cataloghas the full list)Self-learning, typed memory with a review queue, so it grows without polluting itself
A skill router — search your skills, load only what's needed, author new ones at runtime
Playbooks — multi-stage work where a person approves each stage's artifact (e.g. Anthropic's AI-native SDLC:
intent.md→spec.md→plan.md…), bundled as a template — see docs/playbooks.mdThe same brain across models, desktop and mobile — one account, one state
🔌 Real reach — every integration is just data
Email (SMTP send + IMAP read), calendars (CalDAV), HTTP services, MQTT & FTP/FTPS devices, WebDAV cloud, SSH/SFTP
IPP printing & eSCL scanning (straight into Paperless-ngx) on LAN multifunction devices
Event-driven: inbound webhooks (
POST /hooks/<name>, secret/HMAC-verified) turn external events into inbox items; outbound webhooks notifyAn MCP gateway to use other MCP servers' tools — register any integration with one call, no code, no redeploy
🌐 Beyond MCP — drive it over plain HTTP
A native REST API: every tool as
POST /api/v1/tools/<name>with an auto-generated OpenAPI 3.1 spec — plug AICortex into n8n, LangChain, an OpenAI-compatible client or a shell script, no MCP client requiredPer-user API keys: scoped (default-deny), hashed at rest, rate-limited, optional expiry — and a key runs the same per-user areas as an OIDC session, never admin by default
🖥 Built-in admin WebUI — no terminal needed
Every signed-in person manages their own notes, scheduled jobs and playbook runs (and approves playbook stages) at
/ui; admins also get the vault (secret names only — values are write-only), skills, users & roles, services & devices editing and the audit log — language-switchable DE/ENSame SSO as the connector (OIDC + PKCE against your IdP, e.g. Pocket ID), CSRF-guarded, fully self-contained (strict CSP, no CDNs, no build step) — served by the same container, no extra service
Every page takes the same policy path as the MCP tools, so the browser never gets more than the person's AI
See it in screenshots
👥 Team, autonomy & continuity
A presence-aware multi-agent board: live presence, capability-routed task pull, context-preserving handoff
Cross-LLM session handoff — resume exactly where another model or device left off
Cron-as-data scheduling with per-user act-as: a scheduled job runs confined to its owner's area, via a short-lived per-job token — never a standing god-token
Runners carry the jobs out — AICortex wakes your Claude (a Claude Code cloud task on your plan) with the due job, or asks your ChatGPT plan, an API key or a local Ollama; each person sets up their own in the WebUI and gives each job its runner, no extra container (how)
🔒 Secure & multi-tenant
Your own OIDC login and roles (admin / user / viewer) — one switch,
AUTH_ENFORCEPer-user isolation and capability areas: private memory + vault per person, plus default-deny service/skill allow-lists an admin assigns — so not everyone may use every tool
An encrypted secret vault, an SSRF egress guard, and capability checks that fail closed — end-to-end on your hardware, including a local model (Ollama)
Related MCP server: basic-memory
How it works
LLM app — any MCP client (desktop / mobile) · one or many agents
│ custom connector / MCP server (HTTPS, from the model's cloud)
▼
Reverse proxy (Zoraxy / Caddy / nginx / Traefik …)
│
▼
AICortex (this container, on your NAS)
│ uses local files & secrets
▼
Memory · Skills · HTTP services · MQTT & FTP devices · WebDAV cloud · Workspace files
· SSH/SFTP · SMTP + IMAP email · IPP printing · eSCL scanning · MCP gateway · Inbox/Tasks
· Sessions · Cron · Secret vault (every outbound call passes the SSRF egress guard)How AICortex differs
The "LLM brain" space has some great projects — AICortex deliberately sits in a different spot:
GBrain is a memory engine — markdown-first, Postgres/PGLite-backed, with a self-wiring knowledge graph and hybrid/vector search. Deep retrieval, one concern.
CortexPrism is a full agent operating system — its own runtime, ~30 LLM providers, a plugin marketplace, sandboxed code execution and multi-user teams. Batteries included, heavyweight.
AICortex is a single, lightweight MCP connector: one container that gives any client a persistent brain — memory and skills, devices, sessions, secrets and scheduling — where new integrations are added as data, no redeploy. And unlike most brain projects it isn't MCP-only: the same tools are exposed over a native REST API with an auto-generated OpenAPI spec, so non-MCP clients (n8n, LangChain, a script) plug in too. No database, no graph, no marketplace; you bring your own client.
Rule of thumb: want a dedicated memory/graph engine → GBrain; want a batteries-included agent OS → CortexPrism; want a minimal self-hosted connector that plugs your existing LLM app into your own stuff → AICortex.
Try it in 5 minutes
Just want to look around? Run it on your own computer — no NAS, domain or login needed, only Docker:
echo "STORAGE_ENCRYPTION_KEY=$(openssl rand -base64 32 | tr '+/' '-_')" > aicortex.env
docker run -d --name aicortex -p 127.0.0.1:8787:8787 --env-file aicortex.env \
-e ALLOW_INSECURE=1 -v aicortex-data:/data ghcr.io/ikarusmk/aicortex:latestOpen http://localhost:8787/ui — the WebUI, already signed in as admin.
Connect an AI that runs on the same computer. Claude Code:
claude mcp add --transport http aicortex http://localhost:8787/mcp, then ask it to callcortex_start. Other local clients: docs/clients.md. The Claude and ChatGPT apps connect from their provider's cloud, so they need the real setup.Tell it something about you, or "tomorrow at 8: summarise yesterday's notes" — it shows up under Notes and Scheduled jobs. A job runs once you add a runner with an API key (OpenRouter's free models work); waking Claude needs your own login, so that, too, is the real setup.
⚠️ No login in this mode.
ALLOW_INSECURE=1switches authentication off: whoever reaches the port is admin. Keep the port on127.0.0.1as above, keep real secrets out, and never run it like this on a server. For everyday use, follow the Quick start with your own login.
Done looking? docker rm -f aicortex && docker volume rm aicortex-data && rm aicortex.env
removes it with all its data.
Requirements
A NAS or server running Docker (Compose v2).
A reverse proxy serving the container over public HTTPS (cloud-hosted LLM clients connect from their provider's cloud, so the endpoint must be reachable from the internet; a purely local client can reach it on the LAN).
A domain/subdomain pointing at your proxy.
An MCP-capable client that supports custom connectors / MCP servers (Claude, ChatGPT, or any MCP client).
Quick start
🤖 Installing with an AI coding agent? Point it at INSTALL_FOR_AGENTS.md — it walks the agent through the whole install and verification, handing the human-only steps (DNS, proxy, OIDC) back to you.
git clone git@github.com:IkarusMK/AICortex.git
cd AICortex
cp .env.example .env # adjust PUID / PGID / HOST_PORT / TZ
docker compose up -d --buildThe MCP endpoint is served at http://<host>:8787/mcp.
Prebuilt image (no local build): a multi-arch image is published to GHCR by CI. In
docker-compose.yml, comment outbuild: ., uncommentimage: ghcr.io/ikarusmk/aicortex:latest, thendocker compose pull && docker compose up -d.
Updating from 1.x: pull the new image and restart with the same
data/folder and.env— there is no migration step. Connected apps stay signed in; memory, skills, vault, services, tasks, sessions and API keys carry over. What changes is listed in CHANGELOG.md.
Expose it & add the connector:
Point a subdomain (e.g.
agent.example.com) at your reverse proxy.Proxy that host to
http://<nas-ip>:8787over HTTPS — the upstream is plain HTTP, so do not enable "TLS to upstream". If your proxy geo-blocks, allow your LLM provider's egress region. The published port binds to127.0.0.1by default (only this host's proxy reaches it, not the LAN); a host-network proxy useshttp://127.0.0.1:8787, a bridge-network proxy needsBIND_ADDR=0.0.0.0(or a shared Docker network).In your MCP client: add a custom connector / MCP server → URL
https://agent.example.com/mcp. Agents and CLIs that can only send a header (Claude Code, Codex, Hermes, OpenClaw, LM Studio, …) use a per-agent key instead — setup for every client: docs/clients.md.Test: ask the assistant to call
cortex_start. The briefing starts withAICortex v<version>; on a fresh install it is short because the memory is still empty. (pingworks too, but it is not among the eleven tools clients see up front.)
Authentication & authorization
AICortex separates authentication (who may connect) from authorization (what they may do). Set up the login once, then pick your mode with a single line in .env.
1. Authenticate with your own OIDC provider
Exposing the connector publicly requires OAuth. AICortex uses your own identity provider as the login backend — Pocket ID, Authentik, Keycloak, Auth0, anything with standard OIDC discovery. FastMCP's OIDC proxy handles the MCP-side OAuth 2.1 flow (Dynamic Client Registration + PKCE); your provider does the actual login.
Variable | Example |
|
|
| from a client you register in your provider |
|
|
|
|
Register the OAuth client in your provider with redirect URI <BASE_URL>/auth/callback, then (re-)add the connector in your client to be sent through the login.
🟣 Using Pocket ID? Follow the click-by-click Pocket ID setup guide — creating the OIDC client, the exact
.env, and optional groups → roles.
ℹ️ Don't put reverse-proxy SSO / forward-auth in front of
/mcp— an MCP connector is a machine client and can't follow an interactive login redirect. Authentication belongs at the MCP layer, which is exactly what this does. When the OIDC variables are unset,/mcpaccepts bearer tokens only (per-agent keys andRUNNER_TOKEN);ALLOW_INSECURE=1turns all auth off for local testing.
2. Pick your mode — one line in .env
🏠 Homelab | 🏢 Enterprise | |
For | one trusted person (you) | several people on one brain |
Tool access | everyone gets every tool | roles: admin / user / viewer |
Data | one shared brain | per-user memory + private vault |
The line |
|
|
🏠 Homelab — keep it simple
AUTH_ENFORCE=0Every authenticated caller gets every tool — one shared brain, zero friction. Authentication (OIDC) still guards the front door; you've just turned off the internal role gate.
🏢 Enterprise — several people, one brain
AUTH_ENFORCE=1 # default — the single switch (see below)
OIDC_SCOPE=openid profile email groups # request the groups claim
AUTH_ROLE_CLAIM=groups # map Pocket ID groups → rolesAUTH_ENFORCE=1 is the one switch — "enforce means enforce". It turns on roles (admin / user / viewer), per-user isolation, and per-user capability areas together. Roles are driven by your IdP groups (mapped in data/auth/policy.json, or managed live with the tenancy_* admin tools). Each non-admin is confined to their own memory scope and private vault namespace — two people never read or overwrite each other's data — and reaches only the services & skills an admin assigns (default-deny); an admin provisions per-user secrets (secret_set owner=…) and areas (tenancy_set services=… skills=…). Admins keep full access.
Both modes run the same image. Private data (memory, vault) fails open so a glitch never strands the operator; shared capabilities (services, skills) fail closed under enforce. Full guides: docs/authorization.md · docs/per-user-areas.md.
Capabilities
Group | Tools | What it does |
Onboarding |
| Start here — a short briefing for the project at hand; the full catalog on request; |
Router |
| Find and run any tool beyond the eleven listed by default (profile |
Health |
| Connectivity check |
Memory |
| Self-learning, typed facts with dedup + a candidate review queue |
Skills |
| Searchable know-how; learn new skills at runtime |
Services (HTTP) |
| Register & call any HTTP API as data |
Devices (MQTT) |
| Talk to MQTT devices (e.g. a LAN printer or sensor) as data |
Files (FTP/FTPS) |
| Up/list files over FTP/FTPS |
Cloud (WebDAV) |
| Move large files NAS↔cloud (Nextcloud/ownCloud), app-password auth |
Calendar (CalDAV) |
| Read/create/delete calendar events (Nextcloud/Radicale), app-password auth |
Workspace files |
| See & tidy the |
SSH / SFTP |
| Run remote commands & transfer files (hosts as data, vault creds) |
Email (SMTP) |
| Send mail/notifications with optional attachment |
Email (IMAP) |
| Read incoming mail (read-only, PEEK); pull attachments → |
Printing (IPP) |
| Print PDFs/images to a LAN printer via IPP/AirPrint |
Scanning (eSCL) |
| Scan on a LAN device → |
MCP gateway |
| Use other MCP servers' tools as data |
Webhooks |
| Inbound |
Multi-agent |
| Presence-aware team: capability-routed pull & context-preserving handoff |
Sessions |
| Cross-LLM handoff log; auto-expires |
Scheduling |
| Cron jobs as data; each job goes to a runner set up in the WebUI |
Secrets |
| Encrypted vault; values never returned |
Tenancy (admin) |
| Configure per-user data areas — the multi-user control plane, as data |
Guide |
| Self-description (also sent as server |
Every register has a matching delete (skill_delete, service_delete, mqtt_delete, memory_delete, secret_delete, …), so anything you register you can also remove via the connector.
Memory, skills & the skill router
This is the heart of the project — making the assistant itself portable, not just the chat.
Memory lives as plain files under
data/memory; thememory_*tools let the LLM recall and update what it knows about you, the same on every device. The catalog groups it by tier — 🧭 Core (identity & preferences) → 📂 Projects → 🛠 Working style → 🔗 References, derived from each memory'stype, with short-term/current state in the sessions layer — so a fresh LLM sees who you are before the ephemera.Skills live as folders under
data/skills(<skill>/SKILL.md+ resources). The router (skill_search/skill_load/skill_resource) finds the right skill and pulls in only what it needs (progressive disclosure).Categories keep it cheap — and are mandatory.
skill_writerefuses an uncategorized skill and snaps near-duplicate spellings onto the existing category, so a 300-skill library stays a dozen lines incortex_catalog, not a token dump. Starter skills live inexamples/skills/and ship with the image — Skills → Add starter skills in the WebUI adds them.cortex_startfirst. The server instructions ask every model to call it at the start of a conversation (with the project name). The briefing names every general working rule (afeedbackmemory without a project) — tag a rulepinnedto put it first with its text — while rules that belong to a project only come along for that project. For clients that don't pass the instructions on, add a short block to your client's project/system prompt — seedocs/client-project-instructions.md.
Auto-memory — a brain that learns by itself
Most assistants forget the moment a chat ends. AICortex closes that loop — and stays tidy while doing it.
Typed & tiered. Every memory is
user,feedback,projectorreference;memory_writerefuses an untyped memory, andcortex_cataloggroups them into tiers (Core → Projects → Working style → References) so the catalog reads long-term → ephemeral — the brain stays sorted by intent.Learns in-session, at zero extra cost. The model already talking to you distills the durable facts and writes them back — no second model, no background loop.
Dedup-first. A write flags overlapping entries so related facts merge into one file instead of multiplying.
A review queue. Anything captured automatically (or staged with
memory_note) lands as a candidate, not live memory — youmemory_promotethe keepers andmemory_rejectthe rest. SetLEARN_AUTOCAPTURE=0to turn auto-capture off.
Tools & integrations (as data)
New integrations don't need new code. A service is a small config you register at runtime with service_add (stored under data/services); call_service then reaches it — only registered services are allowed, and the auth token is injected server-side from the vault (token_env), never stored in service data or returned to the model. Every service takes a category (like skills — service_add refuses without one), so the catalog stays grouped and findable. Pair a service with a skill_write skill that explains how to use it, and a new capability is live without a redeploy.
Multi-agent — one brain, many agents
Run your assistants as a team: Claude on the desktop, a local model on the NAS, your phone — sharing one brain and one task board.
Live presence.
agent_registerdoubles as a heartbeat;agent_listshows who's online / idle / away, andcortex_catalogsurfaces the team.Pull work, don't hunt for it.
task_next(owner)recommends the best open task (assigned-to-you → capability match → unassigned);task_claimtakes it.Hand off with full context.
task_handoff(id, to)reassigns a task, notifies the recipient, and attaches the work session — theysession_loadand pick up exactly where the other stopped.Namespaced memory. Shared knowledge in
shared, private notes inagents/<name>.
Sub-agent spawning stays client-side; the connector is the shared coordination layer they meet on. See docs/ARCHITECTURE.md.
Autonomy & scheduling
New here? → Scheduled jobs and runners, step by step.
Schedules live on the NAS as data — cron_add(name, schedule, prompt[, runner, once]) from any device, or Scheduled jobs in the WebUI (presets, a preview of the next runs, on/off, the job's runner). Runners carry them out — set up in the WebUI, tab Runners, by every person for their own jobs; no extra container, nothing to install:
Once a minute, on the minute, AICortex checks for due jobs — without any model, so an idle minute costs nothing. A minute it missed (slow run, restart) is caught up for five minutes.
Only when a job is due does it wake or ask the job's runner. A job that names none goes to its person's own default runner (their first one, or the one they chose), else to the standard runner (one of an admin's). A one-off job (
once) switches itself off after its run.The job runs through this connector and reports via your configured channel or the connector inbox;
cron_mark_runrecords the result.
Kinds of runner:
Runner | What AICortex does | You need |
Claude (cloud task) | Wakes a Claude Code cloud task of yours through its API trigger and sends the due job along. Claude runs it in Anthropic's cloud on your own plan and reaches AICortex through your claude.ai connector — even while your computer is off. | A Claude plan with Claude Code on the web; a cloud task with an API trigger, the AICortex connector on, and as its prompt the one fixed sentence the WebUI shows (once — it covers every job); its API address and token in the runner form |
ChatGPT plan | Asks a model on the plan AICortex signed in with (below) | ChatGPT Plus or Pro; one per AICortex, set up by an admin |
API key | Asks the model: Anthropic (Messages API), OpenAI, OpenRouter or Gemini | The key — typed in, or picked from your vault entries |
OpenAI-compatible (admins) | Asks any OpenAI-compatible server, e.g. Ollama in your LAN ( | Its address (allowed in |
A key typed in is stored encrypted as the runner's own vault entry (RUNNER_KEY_<id>, an internal name no service can use) and never shown again; picking an existing entry (e.g. OPENROUTER_API_KEY) needs no second copy — a person may pick only their own entries, an admin shared ones too. Where AICortex asks the model itself, each job is a conversation of its own: AICortex confines it to the job owner's area (act-as) before the model sees it, ends that afterwards and records the run — the model never sees a token or the runner plumbing; its tool calls go through /mcp, so roles and audit apply as for any caller. A job gets five minutes; runners work side by side, so a slow one holds up nobody else. Test on a runner asks its model one tiny question, or wakes the cloud task with a harmless check and shows the link to the Claude session. Limits: 10 runners per person (admins: no limit), keys up to 1,024 characters, job prompts up to 20,000.
A Claude runner acts as you. The cloud task runs with your full rights in AICortex and all your claude.ai connectors, so a Claude runner only carries out jobs you wrote yourself — their prompt, name and recipient — and never ones written with an agent key. Anthropic marks the wake-up text as untrusted data that can't grant approval — like every runner, a job does only what is read-only or explicitly allowed in its prompt. Limits: 30 wake-ups an hour per task (AICortex keeps two minutes between them), no automatic retry (a retried wake-up would start a second session). Why not
claude setup-token? Anthropic refuses a subscription token outside Claude Code (HTTP 429 "Error"), so 2.3.0 dropped that way; a 2.2 setup using it becomes a switched-off Claude runner.
The ChatGPT plan (Plus or Pro) uses OpenAI's Sign in with ChatGPT for open-source apps: choose ChatGPT plan, click Sign in with ChatGPT, open the sign-in and allow AICortex to use the plan. OpenAI then sends the browser to http://127.0.0.1:1455/… — the computer you sit at, not the NAS — so the page shows an error; paste that whole address into the field below and finish. AICortex checks the ID token, keeps the tokens encrypted in the vault, refreshes them itself (the sign-in stays valid as long as AICortex renews it within 30 days, which it does on its own) and runs jobs through OpenAI's Responses API. Leave the model empty for your account's first model, or pick one of the suggestions. On Plus, all apps share the plan's five-hour limit; Sign out revokes the sign-in at OpenAI.
An own runner outside AICortex that calls cron_due with the static RUNNER_TOKEN from the connector's .env keeps working: it gets the jobs assigned to external, and the unassigned ones while no standard runner is on. A job is handed out once per scheduled minute, so two runners never run it twice.
What the runner may do. In homelab mode (
AUTH_ENFORCE=0) it has full access. In enterprise mode (AUTH_ENFORCE=1) it is a least-privilegeuser: it can't register integrations or set secrets, and reaches only the services/skills an admin assigned. For a runner that should act broadly, setRUNNER_ROLE=admin; for scheduled work on a person's behalf, give the job an owner so it runs act-as in that user's area (short-lived per-job token, no standing authority). See docs/authorization.md.Plan or API key? A Claude cloud task and the ChatGPT sign-in are the providers' own ways for this and count against your plan's limits; an API key bills per use. Because a runner is only woken when a job is due, the usage stays small either way.
Run it with a local model (Ollama)
AICortex is model-agnostic — including fully local models. LibreChat is a self-hosted chat UI with first-class MCP support, Ollama runs the model locally, AICortex is the brain/tools — nothing leaves your network. LibreChat connects to your existing AICortex over MCP with per-person OAuth (the same OAuth 2.1 + dynamic-registration flow Claude.ai uses) — no connector changes needed.
Browser ─▶ LibreChat (chat UI + MCP client) ──MCP, per-person OAuth──▶ AICortex ──▶ memory · skills · tools
▼
Ollama (local model)In short: make Ollama reachable on your LAN (OLLAMA_HOST=0.0.0.0), bring up the LibreChat stack (its own compose — never merged into AICortex's), add AICortex as an MCP server (type: streamable-http, requiresOAuth: true), authenticate once through your IdP, and drive it through a curated Agent so a mid-size local model isn't overwhelmed by 100+ tools.
Pick a tool-capable model (recent Qwen/Llama-class; small <7B models struggle with multi-step tools). Full step-by-step guide — compose,
.env, the OAuth connect, troubleshooting, and optional cloud models (OpenRouter, Ollama Cloud): docs/local-llm-librechat.md.
FAQ
Why a separate "brain" when Claude and ChatGPT already have memory? Because processing your data isn't the same as owning it. Built-in memory lives inside one vendor, one app, one model. AICortex is yours: model- and client-agnostic (the same brain on Claude, ChatGPT, a local Ollama model, desktop or phone — no lock-in), it reaches your own devices and files, and it keeps secrets in a vault on your NAS. Its action tools reduce what you paste into a cloud chat rather than add exposure — the sensitive value stays server-side and is referenced by name.
Why keyword search instead of semantic / vector search?
Deliberate. Memory is a curated, typed catalog — the handful of facts you (or the assistant) chose to keep — not an embedding pile you hope to retrieve from. A typed, human-readable catalog stays debuggable, portable and free of a vector database. (If the skill library ever grows large enough that paraphrase-matching hurts, opt-in local embeddings for skill_search only is the escape hatch — not a memory-wide graph.)
Why no built-in code sandbox / executor?
A code executor would be the single biggest attack surface and it breaks the no code, no redeploy principle. The sandbox pattern already exists as data: ssh_add a disposable, LAN-isolated jail host and ssh_run there. It's a documented pattern, not something baked into the connector.
Why no built-in Telegram / Slack / ntfy / … integrations?
Those are just HTTP APIs — so they're service_add + a skill, not new code. That's the whole point: a new capability is data + a skill, never a redeploy.
Do I need OIDC and a reverse proxy? For a public/cloud LLM client, yes — the endpoint must be reachable over HTTPS and authenticated (see Authentication & authorization). Running a purely local client? You can keep it on the LAN. Homelab mode keeps auth simple; enterprise mode adds roles + per-user isolation with one switch — see docs/authorization.md.
Security
Auth fails closed. Without OIDC,
/mcpaccepts only bearer tokens (RUNNER_TOKENor a per-agent key);ALLOW_INSECURE=1runs it open for local development only. Enable OIDC before exposing the proxy for Claude or ChatGPT.DNS rebinding blocked. Browser requests (and the WebUI) must come from an IP address, localhost, a name without a dot, a private-use name or the
BASE_URLhost; native clients are unaffected.MCP_ALLOWED_HOSTSmakes the host check strict.SSRF guard.
service_add/mqtt_add/ftp_addare model-callable, so the registered-target list isn't a trust boundary by itself. Every outbound host is resolved and private / loopback / link-local / cloud-metadata addresses are blocked unless they fall insideINTERNAL_ALLOW_CIDRS(operator-only). Set it to the LAN/VPN ranges you actually use.Encrypted vault, enforced. Secrets go in the vault via
secret_set(encrypted at rest, referenced by name, never returned).secret_setrefuses plaintext unlessSTORAGE_ENCRYPTION_KEYis set..envis only for bootstrap config.Inbound webhooks authenticate themselves.
POST /hooks/<name>is served alongside/mcp/but not behind the MCP OAuth (external senders can't do OAuth) — instead each hook validates a shared secret token (theX-Webhook-Tokenheader only — never a URL query, so it can't leak into logs) and/or an HMAC signature (constant-time). Unknown hooks and bad/unsigned requests both return a uniform401(no hook-name enumeration), the body size is capped, and it only ever deposits into the inbox — it never reaches the tool surface. Expose only/hooks/*past your reverse proxy's auth, never/mcp. Don't want any public receiver? Leave webhooks unregistered (opt-in) or keep the/hookspath LAN-only.TLS verified by default for FTP / MQTT / WebDAV / scanning; self-signed LAN devices opt out per-endpoint via the admin-only registration tools (
tls_insecure/ca_bundle)..envanddata/contents are git-ignored — never commit secrets.
AICortex has been through external security review (fail-closed vault, SSH host-key pinning, connect-time DNS-rebinding protection, resource limits, the authorization layer and per-user isolation). Full status & reporting: SECURITY.md.
🔐 How your secrets stay secret — encrypted at rest, never returned to the model, never in chat/repo, and how to set them safely (
secret_setor.env): docs/secrets.md.
REST API (for non-MCP clients)
Not every client speaks MCP. The native REST layer exposes the same tools over plain HTTP, so n8n, LangChain, an OpenAI-compatible client or a shell script can drive AICortex directly — through the same authorization and per-user areas as an OIDC session.
Three routes, served next to /mcp but authenticated by a per-user API key instead of OAuth:
Route | Purpose |
| The tools this key may call, with JSON schemas |
| Invoke a tool — request body is the JSON arguments |
| An OpenAPI 3.1 spec of this key's tools — load AICortex into any function-calling framework |
An admin mints keys (they run through the same policy, never admin by default):
apikey_create(identity="alice", scopes="memory, fs_read", ttl_days=90)
# → ak_ab12cd34ef56_XXXXXXXX… (shown ONCE — store it now)
apikey_list # keyid prefixes, scopes, expiry — never the secret
apikey_revoke("ab12cd34ef56")Call it with the key as a bearer token:
curl -X POST https://agent.example.com/api/v1/tools/memory_search \
-H "Authorization: Bearer ak_ab12cd34ef56_XXXXXXXX…" \
-H "Content-Type: application/json" \
-d '{"query": "trading rules"}'Security, by design: keys are hashed at rest (constant-time compare) and default-deny — a key reaches only the tools in its scopes allow-list, further narrowed by the identity's role and device areas. Secret-, key- and tenancy-management tools are never reachable via a key. Each key is rate-limited; pass ?stream=1 (or Accept: text/event-stream) for SSE on long-running tools. Full detail: docs/rest-api.md.
Proxy: expose
/api/*(and/hooks/*) past the reverse proxy without OIDC — the API authenticates itself by key. Never expose/mcpthat way.
Admin WebUI
Not everyone wants to manage secrets from a terminal or chat. /ui is a built-in,
self-contained admin panel (no extra container, no build step, no CDNs — strict CSP),
language-switchable DE/EN:
Page | What you can do |
Overview | Version, enforce status, live counts (skills, secrets, services, devices, users) |
Notes (everyone) | Your memories: search, filter by type/project, edit with conflict detection; admins also review auto-captured candidates |
Scheduled jobs (everyone) | Your cron jobs: plain-language schedule, next/last run, on/off, the job's runner, presets with a preview of the next runs |
Runners (everyone) | Your runners: a Claude cloud task, an API key (typed or picked from the vault), an OpenAI-compatible address; state, link to the last Claude session, Test. Admins see all, choose the standard runner and sign in with ChatGPT |
Playbooks (everyone) | Library (adopt the AI-native SDLC template, edit stages) and your runs: progress, artifact versions, approve or reject each stage |
Vault | See secret names; add, edit and delete tokens/API keys/passwords (shared or per-user). Values are write-only — editing sets a new one without showing the old, and none ever leaves the vault |
Skills | Browse by category, view, create/edit/delete skills (same house rules as |
Users | Roles ( |
Connect clients | One key per agent or CLI (Claude Code, Codex, Hermes, LM Studio, …): person, tools profile, allowed tools, expiry; the key is shown once, with the setup snippet for the chosen client |
Services & devices | Every registry (services, MQTT, FTP, printers, scanners, WebDAV, CalDAV, SSH, mail, IMAP, MCP, webhooks): create, edit, delete — forms built from each |
Logs | The authz audit log — who called which tool, allowed/denied and why; filterable, newest first |
Login rides on your existing IdP (authorization code + PKCE): register one extra
redirect URI in the OIDC client — <BASE_URL>/ui/callback — and open <BASE_URL>/ui.
Everyone works in their own area; the management pages are for the admin role. Sessions
are signed HttpOnly cookies, mutations are CSRF-guarded, every action lands in the audit log
(names only, never values). Each page runs the same policy path as the MCP tools, so the
browser gets exactly the rights of the person's AI.
Without OIDC (local testing) the UI is open on the localhost-only bind — the same trust
model as the MCP endpoint itself. There is no separate username/password login: remote
access always goes through your IdP. Opt-out: UI_ENABLED=0.
First steps after the install — sign in, set up people, create keys and connect Claude, ChatGPT, Claude Code and other agents: docs/clients.md → Step by step.
Configuration
All config lives in .env (copy from .env.example):
Variable | Default | Description |
|
| Host port the server is published on (the container always listens on |
|
| Address the published port binds to. Loopback = only this host's reverse proxy reaches it, not the LAN. Set |
|
| User/group ID the process runs as (file ownership) |
|
| Container timezone |
|
| Native REST API ( |
|
| Per-key REST rate limit (requests/minute; |
|
| Admin WebUI ( |
OIDC, authorization and hardening variables are documented inline in .env.example and the Authentication & authorization section.
Deploying on a VPS or over a VPN
The connector is just an HTTPS MCP endpoint, so it runs anywhere Docker does. The SSRF guard keeps that safe:
VPS, public APIs only: same reverse proxy + OIDC, set
BASE_URLto its public URL, leaveINTERNAL_ALLOW_CIDRSempty.Reach home devices from a VPS: link the VPS and your LAN with a VPN (WireGuard, Tailscale, …) and add the VPN/remote subnet to
INTERNAL_ALLOW_CIDRS(e.g.100.64.0.0/10for Tailscale).Multi-site: scope
FORWARDED_ALLOW_IPSto the proxy, list only trusted subnets inINTERNAL_ALLOW_CIDRS, and use a dedicated OIDC client.
Project structure
AICortex/
├── app/ # Server code (FastMCP) — one module per tool group
│ ├── server.py # entrypoint — wires auth + registers tool modules
│ ├── memory.py · skills.py · services.py · mqtt_tools.py · ftp_tools.py
│ ├── webdav_tools.py · fs_tools.py · ssh_tools.py · mail_tools.py
│ ├── print_tools.py · scan_tools.py · mcp_gateway.py · coordination.py
│ ├── cron.py · sessions.py · secrets_store.py · bootstrap.py · guide.py
│ ├── runners.py · runner_builtin.py · runner_cloud.py · chatgpt_signin.py # runners for scheduled jobs
│ ├── authz.py · tenancy.py · pocketid_proxy.py · netguard.py · learn.py
│ ├── webui.py # admin WebUI (/ui) — OIDC+PKCE browser login
│ ├── webui_static/ # its self-contained frontend (no build step)
│ └── requirements.txt
├── data/ # Persistent, human-readable state (git-ignored content)
│ ├── memory/ · skills/ · services/ · mqtt/ · ftp/ · webdav/ · ssh/ · mail/
│ ├── coordination/ · cron/ · sessions/ · printers/ · scanners/
│ ├── vault/ # encrypted secrets
│ ├── auth/ # OAuth client registrations + policy.json
│ └── work/ # workspace file hub (scans, downloads, fs_*)
├── docs/ # Architecture, authorization, local-LLM & client guides
├── Dockerfile · entrypoint.sh · docker-compose.yml · .env.exampleDependencies are baked into the image at build time — no install-on-start volume.
data/,logs/andsecrets/keep their structure via.gitkeep; their contents are git-ignored.
Troubleshooting
See what's wrong first
A handful of read-only commands surface almost everything. Replace the placeholders (<compose-dir>, <device-ip>) with your own values.
# 1) Container logs — startup banner + live errors
docker logs --tail 50 aicortex
# 2) Verbose auth logging (then re-read the logs)
# docker-compose.yml → environment: FASTMCP_LOG_LEVEL: "DEBUG"
# 3) Which version / feature is actually running in the container?
docker exec aicortex python -c "import netguard; print('has tls_verify:', hasattr(netguard,'tls_verify'))"
# 4) Authorization decisions — who called what, allow/deny (per-person 'sub')
tail -n 20 <compose-dir>/data/auth/audit.log # set AUTH_AUDIT_ALL=1 to log allowed calls too
# 5) Can the container actually reach a device? (verify off = ignore self-signed)
docker exec aicortex python -c "import httpx; print(httpx.get('https://<device-ip>/', verify=False, timeout=5).status_code)"After pulling a new image, restart your LLM client session — don't just "refresh tools". MCP clients freeze the tool list at connect, so new tools/parameters only appear on a fresh session. If a new parameter still doesn't show after
docker compose pull && up -d --force-recreate, confirm the container is really on the new image with command #3 above.
Common issues & fixes
Symptom | Cause & fix |
Login succeeds, client says "returned an error when connecting"; logs show | The proxy-issued token doesn't carry upstream scopes as claims. Don't set |
| Behind a TLS-terminating proxy, uvicorn ignored |
|
|
| The OAuth client store was ephemeral. Persistent |
Connector won't connect; proxy returns a login web page | Reverse-proxy SSO / forward-auth in front of |
After setting | The published port now listens on loopback only, but your reverse proxy still forwards to the host's LAN IP ( |
REST call | The key is disabled/expired, or |
REST call returns 404 |
|
IdP consent button spins forever; browser console shows | The upstream |
| The SSRF guard blocks private IPs by default. Add the device's range to |
Scan / WebDAV fails or falls back to plain HTTP (e.g. "did not start a job … 404") | The LAN device serves HTTPS with a self-signed certificate, which is verified by default. Confirm with |
A new tool or parameter from an update doesn't appear | Your LLM client cached the old tool schema. Fully restart the client session (re-adding the connector is often not enough). |
Pulled a new image but old behaviour persists | The container didn't recreate (or |
The assistant says the AICortex tools "aren't loaded" / it can't call | A client-side setting — the connector's tools aren't enabled for that conversation. In the chat's tools/connectors menu, toggle AICortex on as a tool source (web/mobile); if it won't stick, remove the connector, fully reopen the app, re-add it, start a fresh chat. See docs/client-project-instructions.md. |
Support ☕
AICortex is free and open source, and it will stay that way — no paid tier, no features held back.
If it keeps your assistant's memory going and you feel like saying thanks, a coffee is always gladly received:
Not in the mood for coffee? These help just as much:
⭐ A star — so others looking for a self-hosted brain find the project.
🐛 An issue — when something is wrong, unclear or could be better.
💬 A word to a friend — who wishes their assistant would finally remember them.
License
Apache License 2.0 © 2026 IkarusMK
Available Tools
11 toolsbootstrapBootstrapARead-onlyIdempotent
Start-of-conversation briefing, identical to cortex_start: who the user is, how they want you to work and the project's notes, tasks and last session. Kept so project instructions that say "call bootstrap" keep working; new setups call cortex_start.
| Name | Required | Description | Default |
|---|---|---|---|
| task | No | What you are about to do, in a few words; finds matching skills and integrations | |
| project | No | Project the conversation is about, e.g. "Birdhouse"; empty = list the known projects |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and openWorldHint=false, so the safety profile is covered. The description adds genuinely useful context beyond that: what categories of state the briefing pulls in and its legacy/duplicate status relative to cortex_start. It does not mention cost or latency, which is the only notable omission.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, each carrying distinct information: what it returns, why the alias exists, and the migration path. Nothing is repeated from the schema or annotations.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema, complete annotation coverage, and fully documented parameters, most structured needs are met, and the description supplies the one thing structured data cannot: the deprecation/migration story. A brief note on how the task parameter shapes results would make it fully self-contained, but the gap is minor.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the schema already explains both task and project, including that an empty project lists known projects. The description says nothing further about the parameters, so the schema does all the semantic work — the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action (a start-of-conversation briefing) and enumerates exactly what it returns: user identity, working preferences, project notes, tasks, and last session. It also names the sibling cortex_start as identical, so an agent can distinguish the two without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit routing rules: use this only because existing project instructions say 'call bootstrap', while new setups should call cortex_start instead. Both the when-to-use and the alternative are stated, not implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
cortex_startAICortex startARead-onlyIdempotent
Call this first in every conversation. Returns a briefing: who the user is
and how they want you to work; for project its notes, open tasks and last
session (a project with no notes yet says so); for task matching skills and
integrations. Without arguments: that profile and the known projects. At most
4,000 characters — longer lists are cut and marked "shortened". bootstrap is
the same for older setups; later in the conversation use memory_search for a
fact and task_list for the task board. Example:
cortex_start(project="Birdhouse", task="slice a bracket for the printer").
| Name | Required | Description | Default |
|---|---|---|---|
| task | No | What you are about to do, in a few words; finds matching skills and integrations | |
| project | No | Project the conversation is about, e.g. "Birdhouse"; empty = list the known projects |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only, idempotent, closed-world behavior, yet the description still adds real operational facts: a 4,000-character output cap with truncation flagged as 'shortened', and the explicit empty-state behavior for a project with no notes. It stops short of describing permissions or cost/latency, so it is strong but not exhaustive.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The most important instruction ('Call this first') is front-loaded, and the rest is dense but information-bearing. Some phrasing is compressed to the point of awkwardness ('a project with no notes yet says so'), and the trailing example costs length, but nothing is truly wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the description need not detail return structure, yet it sketches enough of the briefing's sections that the agent knows what to expect, and both optional parameters and the no-argument default path are covered. Remaining gaps (error behavior, what 'skills matched' means) are minor.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so both parameters are already documented, but the description adds meaning the schema does not: what each argument changes about the returned payload (project yields notes/tasks/last session; task yields skills and integrations) and a worked example call. The added value is real though partly overlapping with existing parameter descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a concrete action (return a briefing) and enumerates the content by mode: profile/working preferences, plus project notes, open tasks and last session for `project`, and matching skills and integrations for `task`. It explicitly contrasts itself with bootstrap, memory_search and task_list, so an agent can place it among siblings without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an unambiguous trigger ('Call this first in every conversation'), explains the no-argument case, and routes later needs elsewhere ('use memory_search for a fact and task_list for the task board'). It even resolves the overlap with the bootstrap sibling by naming it as the equivalent for older setups.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
integration_callIntegration callADestructive
Run a tool found with integration_search and return its result unchanged — its own errors too; a name you may not call is refused with a pointer to integration_search. Same permissions as calling the tool directly: one that changes or deletes data does so here as well, so confirm such steps as you would there. Example: integration_call(name="print_document", arguments={"printer": "office", "file": "scan.pdf"}).
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Tool name exactly as integration_search returned it | |
| arguments | No | That tool's parameters as an object |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds real context beyond the annotations: results and errors pass through unchanged, refused names point back to search, and permissions match a direct invocation — which importantly explains that destructiveHint is inherited from the target tool rather than universal. What's missing is any note on side effects like rate limits, retries, or whether arguments are validated against the target schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core verb and result-passthrough behavior, then adds refusal routing, then the safety caveat, then a single example. Every sentence carries a distinct fact and the example earns its place by demonstrating the two-field call shape.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter passthrough dispatcher with no output schema, the one thing an agent needs is what comes back — the description answers that ('its own errors too', result returned unchanged). Combined with inherit-permissions disclosure and the call example, nothing essential is absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3; the description goes further with a worked example showing name paired with an arguments object, making the passthrough relationship concrete. It does not explain how per-tool parameter schemas are discovered or how to handle argument mismatches.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('run a tool found with integration_search and return its result unchanged') and implicitly contrasts with the sibling integration_search, which only discovers tools. An agent can tell execution apart from discovery without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent: names must come from integration_search, and an uncallable name is refused with a pointer back to integration_search. It also gives an operational rule for destructive targets ('confirm such steps as you would there'), so when-to-use and when-to-be-careful are both stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
integration_searchIntegration searchARead-onlyIdempotent
Find a tool beyond the core set — devices, services, files, mail, calendars, printing, scanning, SSH, webhooks, scheduling, playbooks, agents and administration. Returns matches best first, each with its parameters (* = required); no match says so. Run one with integration_call. For how-to instructions use skill_search, for stored facts memory_search. Example: integration_search("print a PDF on the office printer").
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Most matches, 1–20 | |
| query | Yes | What you want to do, in plain words (any language) |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnly/idempotent/openWorld annotations, it discloses result ordering ('matches best first'), the shape of each result ('each with its parameters (* = required)'), and the empty-result behavior ('no match says so'). That is meaningful behavioral context, though it says nothing about match quality thresholds or whether results are cached/stale.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then alternatives, then the follow-up tool, then an example — a sensible information order with no filler sentences. The domain enumeration is long but earns its place by bounding scope; a slightly tighter list would make it a 5.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and full schema coverage, the description only needs to explain routing and result semantics, and it does both plus the call-chain to integration_call. The only omission is any hint about result-count behavior or what to do when matches are poor, which is minor for a two-parameter search tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, and the description adds a concrete worked example ('print a PDF on the office printer') that clarifies the query parameter is a natural-language task rather than a keyword. The limit parameter is left entirely to the schema, which is acceptable.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb+resource ('Find a tool beyond the core set') and enumerates the concrete domains covered (devices, services, files, mail, printing, SSH, webhooks, playbooks, agents). It explicitly separates itself from skill_search and memory_search, so an agent can route without inspecting any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states the use case (discovering non-core tools), names two sibling alternatives with the conditions that select them ('For how-to instructions use skill_search, for stored facts memory_search'), and names the follow-up action ('Run one with integration_call'). When-to-use, when-not-to-use, and alternatives are all present.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_readMemory readARead-onlyIdempotent
Read a memory's full content and version by its name — find the name with memory_search. An unknown name returns "No memory named …".
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | The name memory_search returned (before the ' — ') | |
| scope | No | Memory area: "shared" (default) or "users/<id>"; a person limited to their own area stays there anyway | shared |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, closed-world), so the description's job is to add context, which it does: it discloses the return shape (content plus version) and the exact error text for an unknown name. It stops short of describing whether storage is per-scope or how a miss interacts with the default scope.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two compact sentences with the primary action and return value front-loaded, followed by the prerequisite and the failure behavior. Every clause carries information; there is no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple two-parameter read tool with a full output schema and strong annotations, this is nearly complete: the lookup key, the return contents, and the error behavior are all covered. The only omission is any mention of the scope parameter's effect on which memory is found.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so both parameters are already documented, including the 'before the —' parsing hint and the scope values. The description adds no meaning about the name or scope parameters beyond what the schema says, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (read) and resource (a memory), plus what is returned (full content and version) and the key it is addressed by (name). It also names the sibling memory_search as the way to obtain that key, so an agent can distinguish it from the search tool without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent to memory_search first to obtain the name, which is a real when-to-use instruction rather than vague context. It also documents the failure path for an unknown name, but it gives no guidance on when to prefer this over other siblings or what the scope choice implies for retrieval.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_searchMemory searchARead-onlyIdempotent
Find memories by words (any order); best matches first — a hit in the title counts most, then tags/project, then the text. Returns name, title and a snippet; memory_read gives the full text.
| Name | Required | Description | Default |
|---|---|---|---|
| tag | No | Only memories with this tag | |
| type | No | Only this type: user, feedback, project or reference | |
| limit | No | Most results, 1–100 | |
| query | Yes | Words to look for, any order | |
| scope | No | Memory area: "shared" (default) or "users/<id>"; a person limited to their own area stays there anyway | shared |
| project | No | Only this project's memories |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and openWorldHint=false, so safety is covered. The description adds genuinely new behavioral context: relevance ordering with a documented priority (title hits weigh most, then tags/project, then text) and the fact that results are truncated to name, title and a snippet. It does not mention result-count limits or pagination behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two compact sentences, zero filler, with the core search-and-rank behavior front-loaded and the sibling hand-off placed last where it is most actionable. Every clause conveys distinct information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and full schema coverage on six parameters, the description need only cover purpose, ranking and the read-follow-up, which it does. Minor gaps remain around scope semantics ('shared' vs 'users/<id>') and default result limits, though those are already documented in the schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents tag, type, limit, query, scope and project. The description only reinforces the query semantics ('words... any order') and loosely gestures at tag/project via the ranking note, adding little beyond the structured fields. Baseline 3 applies when the schema carries the parameter burden.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource ('Find memories by words') and immediately defines the matching and ranking model (title > tags/project > text). It also explicitly separates this tool from its closest sibling by noting that 'memory_read gives the full text', so an agent can distinguish the two without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear routing guidance by naming memory_read as the follow-up for full content, implying this tool is the discovery/search step. It stops short of stating explicit exclusions, such as when to prefer skill_search or integration_search, or when a filtered list versus a keyword search is appropriate.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_writeMemory writeADestructive
Save a durable, TYPED memory and write it back to the brain.
type (REQUIRED) = one of: user (who the user is / preferences) ·
feedback (how you should work — corrections & confirmed approaches) ·
project (ongoing work, goals, status — give it a review date) ·
reference (pointers to resources/URLs/IDs). An untyped memory is REFUSED
so the brain stays sorted. Only store DURABLE facts (not session chatter),
nothing already in code/git, and NEVER secrets (those go to the vault:
secret_set via integration_call). Writing the same title again REPLACES that
memory (read it first and fold the old content in); the dedup hint flags
related titles to merge.
| Name | Required | Description | Default |
|---|---|---|---|
| tags | No | Comma-separated, e.g. "garden, mqtt" | |
| type | Yes | Required — one of the four types above | |
| scope | No | Memory area: "shared" (default) or "users/<id>"; a person limited to their own area stays there anyway | shared |
| title | Yes | Short title; it names the memory | |
| review | No | YYYY-MM-DD: from then on cortex_start lists it as due for review | |
| source | No | Where the fact comes from, e.g. a URL; default "session" | |
| content | Yes | The fact itself, in Markdown | |
| project | No | Project this fact belongs to, e.g. "Birdhouse"; empty = general | |
| expected_version | No | The version you read with memory_read; refused if it changed since. 0 = don't check |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructiveHint=true and readOnlyHint=false, and the description explains exactly why: writing the same title REPLACES the existing memory. It further discloses that untyped memories are REFUSED and that a dedup hint surfaces related titles to merge — behavior not derivable from the schema or annotations. Minor gap: it doesn't state whether replacement can be undone.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense and front-loaded, with the required-type rule stated before the exclusions. Heavy capitalization and parenthetical asides make it slightly noisy, but nearly every clause carries actionable content rather than restating the name.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a nine-parameter mutation tool with an output schema and full annotation coverage, the description covers the remaining unknowns an agent needs: the type taxonomy, the refusal on missing type, the destructive title-overwrite, the dedup hint, and where secrets belong instead.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline would be 3, but the description adds real meaning beyond it: it defines what each of the four type values is for (user/feedback/project/reference) and gives a usage rule for review on project memories. The title/overwrite interaction is also clarified in prose rather than left to the field description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource ("Save a durable, TYPED memory") and immediately distinguishes itself from the read/search siblings by emphasizing typed durability and write-back. The four enumerated types make the tool's identity unmistakable.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use criteria (durable facts only, not session chatter, nothing already in code/git) and an explicit routing rule for the excluded case: secrets go to the vault via secret_set through integration_call. It also instructs the agent to read before rewriting the same title, pointing at memory_read.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
session_saveSession saveA
Append a checkpoint to a work session so another LLM/device can resume it — for handing over work in progress; durable facts belong in memory_write. Returns the session id. Old sessions auto-expire after SESSION_RETENTION_DAYS.
| Name | Required | Description | Default |
|---|---|---|---|
| model | No | Which LLM you are, e.g. "claude" or "chatgpt" | |
| title | No | Title of a new session; its id is made from it | |
| status | No | e.g. "open" (default), "blocked" or "done" | open |
| project | No | Project this work belongs to; cortex_start shows its latest session | |
| summary | Yes | What happened and where it stands, in a few lines | |
| next_steps | No | What to do next, for whoever resumes | |
| session_id | No | Id from an earlier session_save to continue it; empty = new session |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare non-read-only, non-destructive, closed-world, but do not say what persistence semantics to expect; the description adds that it appends (rather than replaces), that a session id comes back, and that old sessions expire after SESSION_RETENTION_DAYS. It does not say whether appending to an existing session merges or overwrites a prior summary, which is the one behavioral detail left open.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, each carrying distinct information, with the core operation and its purpose front-loaded and the retention caveat last. No restatement of the name or title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists and annotations carry the safety profile, so the description only needs the handover intent, the memory_write boundary and retention, all of which are present. What remains thin is session lifecycle detail, such as what happens when a session_id is reused or a title collides, but nothing needed to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so every parameter including model, title, project, status, summary, next_steps and session_id is already documented in the schema. The description's mention of resume-by-another-device and the returned session id adds framing but no new syntax or format detail beyond what the schema states.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a concrete verb and resource ('Append a checkpoint to a work session') plus the reason it exists ('so another LLM/device can resume it'). It also separates itself from the sibling memory_write in the same breath, so an agent can route without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'For handing over work in progress; durable facts belong in memory_write' states both the intended use and the alternative with the condition that selects it. The routing decision between persistent memory and transient session checkpoints is explicit rather than implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
skill_loadSkill loadARead-onlyIdempotent
Load a skill's full instructions (its whole SKILL.md) by name — find the name with skill_search. An unknown name, or a skill outside your allowed set, gets a message saying so.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | The name skill_search returned (before the ' — ') |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and openWorldHint=false, so the safety profile is covered. The description adds context the annotations cannot: unknown names and skills outside the allowed set produce a message rather than a hard failure, and the return is the entire SKILL.md (potentially large). It does not discuss size or truncation limits, which keeps it from a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short clauses, front-loaded with the action and payload, followed by the prerequisite and the failure mode. No redundant restatement of the title or name.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return shape needn't be described. For a one-parameter read tool, the definition covers the action, payload scope, prerequisite, and error behavior — everything an agent needs to invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already explains the ' — ' delimiter, so the baseline is 3. The description adds a constraint not encoded in the schema — that the name must fall within the caller's allowed set — which meaningfully informs how to choose the value.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb and resource ("Load a skill's full instructions (its whole SKILL.md) by name"), which tells the agent both the action and the scope of the payload. It also names the sibling tool skill_search as the source of the name, so an agent can distinguish it from skill_search and memory_search without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It tells the agent the prerequisite step (find the name with skill_search) and notes what happens for an unknown or disallowed name. What's missing is an explicit statement of when to load versus when a search result alone suffices, but the implied usage is clear given the sibling set.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
skill_searchSkill searchARead-onlyIdempotent
Find skills for a task: up to 10 lines "name — description", best first (a word in the name counts most, then description, tags, text); no match says so. Call this before specialized work, then skill_load the best match.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | The task in plain words | |
| category | No | Only skills in this category; empty = all |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and closed-world behavior, so the safety profile is covered. The description adds genuinely new behavior: result cap of 10, the ranking signals (name weighted most, then description, tags, text), and that no match is reported rather than returning an empty list silently.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two compact sentences, front-loaded with the outcome and the result format, with the ranking rule parenthesized rather than buried. The parenthetical is dense but each clause carries real information; no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema and full annotation coverage, the description only needs to cover purpose, ranking, and workflow placement — all present. The one omission is that it never mentions how the optional category filter narrows results, though the schema covers it adequately.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so both parameters are already documented and the baseline is 3. The description earns an extra point by explaining how the query string is matched — name matches outrank description, tags, and full text — which is meaning the schema does not convey. It says nothing about the category filter's effect on results.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Find skills for a task') and immediately specifies the return shape (up to 10 lines 'name — description', best first). It also implicitly distinguishes itself from the sibling skill_load, which fetches the chosen skill rather than discovering it.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit sequencing: 'Call this before specialized work, then skill_load the best match.' That names the alternative tool and the condition that selects it, so an agent knows both when to use this tool and what to do next.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
task_listTask listARead-onlyIdempotent
List the tasks on the shared agent board — work handed between agents — oldest first, every match (id, status, owner, title). Check it before adding or claiming one (task_add / task_claim via integration_call).
| Name | Required | Description | Default |
|---|---|---|---|
| owner | No | Agent holding the task, as after owner= in the list; empty = any | |
| status | No | open, claimed, blocked or done; empty = all |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and openWorldHint=false, so safety is covered; the description adds behavior the annotations don't: ordering is "oldest first" and results are exhaustive ("every match"). It doesn't discuss result limits or volume, but with an output schema present and the exhaustiveness claim stated, the gap is minor.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences: the first defines the resource, ordering, and returned fields; the second routes the agent to the right next action. Nothing is repeated and the scoping information is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-optional-parameter read tool with full schema coverage, annotations and an output schema, the description covers purpose, ordering, exhaustiveness and the follow-up workflow. Only edge details such as whether done tasks are included by default or any result-size ceiling are left implicit.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, with both owner and status fully documented including the empty-string default semantics, so the schema carries the burden and the baseline of 3 applies. The description hints at filtering only implicitly via "every match" and adds no syntax or enum detail beyond the schema (status enum lives in the schema text, not the parameter enum list).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("List the tasks on the shared agent board") and disambiguates what a task is ("work handed between agents"), plus the return fields (id, status, owner, title). The read-only list role is clearly distinct from the write siblings referenced later (task_add / task_claim via integration_call).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly prescribes when to call it ("Check it before adding or claiming one") and names the alternatives for the write path (task_add / task_claim reached through integration_call). An agent knows both the trigger condition and where to go instead for mutation.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
v0.1.4- Changed
integration_search2 fields changed- added
Input schema / properties / limit / descriptionAdded value: +"Most matches, 1–20" - added
Input schema / properties / query / descriptionAdded value: +"What you want to do, in plain words (any language)"
- Changed
memory_search2 fields changed- added
Input schema / properties / limit / descriptionAdded value: +"Most results, 1–100" - added
Input schema / properties / query / descriptionAdded value: +"Words to look for, any order"
- Changed
memory_write3 fields changed- added
Input schema / properties / content / descriptionAdded value: +"The fact itself, in Markdown" - added
Input schema / properties / title / descriptionAdded value: +"Short title; it names the memory" - added
Input schema / properties / type / descriptionAdded value: +"Required — one of the four types above"
- Changed
session_save2 fields changed- added
Input schema / properties / summary / descriptionAdded value: +"What happened and where it stands, in a few lines" - added
Input schema / properties / title / descriptionAdded value: +"Title of a new session; its id is made from it"
- Changed
skill_search1 field changed- added
Input schema / properties / query / descriptionAdded value: +"The task in plain words"
10 tool updates
v0.1.2- Changed
bootstrap2 fields changed- added
Input schema / properties / project / descriptionAdded value: +"Project the conversation is about, e.g. \"Birdhouse\"; empty = list the known projects" - added
Input schema / properties / task / descriptionAdded value: +"What you are about to do, in a few words; finds matching skills and integrations"
- Changed
cortex_start2 fields changed- added
Input schema / properties / project / descriptionAdded value: +"Project the conversation is about, e.g. \"Birdhouse\"; empty = list the known projects" - added
Input schema / properties / task / descriptionAdded value: +"What you are about to do, in a few words; finds matching skills and integrations"
- Changed
integration_call2 fields changed- added
Input schema / properties / arguments / descriptionAdded value: +"That tool's parameters as an object" - added
Input schema / properties / name / descriptionAdded value: +"Tool name exactly as integration_search returned it"
- Changed
memory_read2 fields changed- added
Input schema / properties / name / descriptionAdded value: +"The name memory_search returned (before the ' — ')" - added
Input schema / properties / scope / descriptionAdded value: +"Memory area: \"shared\" (default) or \"users/<id>\"; a person limited to their own area stays there anyway"
- Changed
memory_search4 fields changed- added
Input schema / properties / project / descriptionAdded value: +"Only this project's memories" - added
Input schema / properties / scope / descriptionAdded value: +"Memory area: \"shared\" (default) or \"users/<id>\"; a person limited to their own area stays there anyway" - added
Input schema / properties / tag / descriptionAdded value: +"Only memories with this tag" - added
Input schema / properties / type / descriptionAdded value: +"Only this type: user, feedback, project or reference"
- Changed
memory_write6 fields changed- added
Input schema / properties / expected_version / descriptionAdded value: +"The version you read with memory_read; refused if it changed since. 0 = don't check" - added
Input schema / properties / project / descriptionAdded value: +"Project this fact belongs to, e.g. \"Birdhouse\"; empty = general" - added
Input schema / properties / review / descriptionAdded value: +"YYYY-MM-DD: from then on cortex_start lists it as due for review" - added
Input schema / properties / scope / descriptionAdded value: +"Memory area: \"shared\" (default) or \"users/<id>\"; a person limited to their own area stays there anyway" - added
Input schema / properties / source / descriptionAdded value: +"Where the fact comes from, e.g. a URL; default \"session\"" - added
Input schema / properties / tags / descriptionAdded value: +"Comma-separated, e.g. \"garden, mqtt\""
- Changed
session_save5 fields changed- added
Input schema / properties / model / descriptionAdded value: +"Which LLM you are, e.g. \"claude\" or \"chatgpt\"" - added
Input schema / properties / next_steps / descriptionAdded value: +"What to do next, for whoever resumes" - added
Input schema / properties / project / descriptionAdded value: +"Project this work belongs to; cortex_start shows its latest session" - added
Input schema / properties / session_id / descriptionAdded value: +"Id from an earlier session_save to continue it; empty = new session" - added
Input schema / properties / status / descriptionAdded value: +"e.g. \"open\" (default), \"blocked\" or \"done\""
- Changed
skill_load1 field changed- added
Input schema / properties / name / descriptionAdded value: +"The name skill_search returned (before the ' — ')"
- Changed
skill_search1 field changed- added
Input schema / properties / category / descriptionAdded value: +"Only skills in this category; empty = all"
- Changed
task_list2 fields changed- added
Input schema / properties / owner / descriptionAdded value: +"Agent holding the task, as after owner= in the list; empty = any" - added
Input schema / properties / status / descriptionAdded value: +"open, claimed, blocked or done; empty = all"
11 tool updates
v0.1.0- First observed
bootstrap - First observed
cortex_start - First observed
integration_call - First observed
integration_search - First observed
memory_read - First observed
memory_search - First observed
memory_write - First observed
session_save - First observed
skill_load - First observed
skill_search - First observed
task_list
TDQS
Scored across 11 tools
The search/read/load pairs are carefully differentiated: memory_search returns snippets then memory_read full text, skill_search finds names then skill_load loads SKILL.md, integration_search finds tools then integration_call runs them. The exception is bootstrap vs cortex_start, which are documented as functionally identical, giving an agent a genuine coin-flip on the first call. The remaining tools (memory_write, session_save, task_list) have clearly separate purposes.
Nine of eleven tools follow a clean resource_verb snake_case pattern (memory_read, skill_load, integration_call, session_save, task_list). bootstrap is a bare noun and cortex_start inverts the word order (prefix + verb), but neither is cryptic and both are readable in context.
Eleven tools is well within a healthy range for a personal-assistant/brain server spanning memory, skills, integrations, sessions and tasks. Each core tool earns its place by covering a distinct capability surface rather than duplicating another. Extra operations (task_add, task_claim, secret_set) are correctly pushed behind integration_call instead of bloating the direct surface.
Memory, skills and integrations each have a coherent find-then-act lifecycle, and write-replaces-title covers memory updates. Two minor asymmetries remain: there is no memory delete/forget tool, and session_save has no matching session_read (resume relies on cortex_start surfacing the last session). Task creation and claiming are reachable only indirectly via integration_call, which is workable but less discoverable than task_list being first-class.
Maintenance
Related MCP Connectors
Persistent memory, knowledge graph, task management and more for AI agents via MCP.
Private-by-default, local-first memory/context/task orchestrator for MCP apps and agents.
Governed personal world model and memory for your AI agent. Pair once, connect over MCP.
Persistent, portable memory for AI assistants — your private memory graph, from any MCP client.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceProvides semantic memory and persistent storage for Claude, leveraging ChromaDB and sentence transformers for enhanced search and retrieval capabilities.31,479 PyPI2,090Apache 2.0
- AlicenseAqualityAmaintenanceBasic Memory is a knowledge management system that allows you to build a persistent semantic graph from conversations with AI assistants. All knowledge is stored in standard Markdown files on your computer, giving you full control and ownership of your data. Integrates directly with Obsidan.md1219,662 PyPI4,496AGPL 3.0
- AlicenseNot gradedqualityBmaintenanceBuild Real-Time Knowledge Graphs for AI Agents32,562Apache 2.0
- AlicenseNot gradedqualityDmaintenanceMCP server providing persistent brain storage for LLM agents, including memory, personality, social intelligence, and context management.1MIT